From 9fb59fbd533d1aef1e8e139ff602393c7983b517 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Wed, 16 Mar 2022 10:39:08 -0700 Subject: [PATCH 01/18] Workflow's for syncing with upstream, build, unit test, and test-runner --- .github/workflows/ci.yml | 236 +++++++++++++++++++++++++++ .github/workflows/pw-to-pr-email.txt | 16 ++ .github/workflows/pw-to-pr.json | 14 ++ .github/workflows/schedule_work.yml | 43 +++++ 4 files changed, 309 insertions(+) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/pw-to-pr-email.txt create mode 100644 .github/workflows/pw-to-pr.json create mode 100644 .github/workflows/schedule_work.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 000000000..8e140ad8c --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,236 @@ +name: IWD CI + +# +# The basic flow of the CI is as follows: +# +# 1. Get all inputs, or default values, and set as 'setup' job output +# 2. Find any cached binaries (hostapd, wpa_supplicant, kernel etc) +# 3. Checkout all dependent repositories +# 4. Tar all local files. This is an unfortunate requirement since github jobs +# cannot share local files. Since there are multiple CI's acting on the same +# set of repositories it makes more sense to retain these and re-download +# them for each CI job. +# 5. Run each CI, currently 'main' and 'musl'. +# * 'main' is the default IWD CI which runs all the build steps as well +# as test-runner +# * 'musl' uses an alpine docker image to test the build on musl-libc +# +# Both CI's use the 'iwd-ci-v2' repo which calls into 'ci-docker'. The +# 'ci-docker' action essentially re-implements the native Github docker +# action but allows arbitrary options to be passed in (e.g. privileged or +# mounting non-standard directories) +# + +on: + pull_request: + workflow_dispatch: + inputs: + tests: + description: Tests to run (comma separated, no spaces) + default: all + kernel: + description: Kernel version + default: '5.16' + hostapd_version: + description: Hostapd and wpa_supplicant version + default: '2_10' + ell_ref: + description: ELL reference + default: refs/heads/workflow + + repository_dispatch: + types: [ell-dispatch] + +jobs: + setup: + runs-on: ubuntu-22.04 + outputs: + tests: ${{ steps.inputs.outputs.tests }} + kernel: ${{ steps.inputs.outputs.kernel }} + hostapd_version: ${{ steps.inputs.outputs.hostapd_version }} + ell_ref: ${{ steps.inputs.outputs.ell_ref }} + repository: ${{ steps.inputs.outputs.repository }} + ref_branch: ${{ steps.inputs.outputs.ref_branch }} + steps: + # + # This makes CI inputs consistent depending on how the CI was invoked: + # * pull_request trigger won't have any inputs, so these need to be set + # to default values. + # * workflow_dispatch sets all inputs from the user input + # * repository_dispatch sets all inputs based on the JSON payload of + # the request. + # + - name: Setup Inputs + id: inputs + run: | + if [ ${{ github.event_name }} == 'workflow_dispatch' ] + then + TESTS=${{ github.event.inputs.tests }} + KERNEL=${{ github.event.inputs.kernel }} + HOSTAPD_VERSION=${{ github.event.inputs.hostapd_version }} + ELL_REF=${{ github.event.inputs.ell_ref }} + REF="$GITHUB_REF" + REPO="$GITHUB_REPOSITORY" + elif [ ${{ github.event_name }} == 'repository_dispatch' ] + then + TESTS=all + KERNEL=5.16 + HOSTAPD_VERSION=2_10 + ELL_REF=${{ github.event.client_payload.ref }} + REF=$ELL_REF + REPO=${{ github.event.client_payload.repo }} + else + TESTS=all + KERNEL=5.16 + HOSTAPD_VERSION=2_10 + ELL_REF="refs/heads/workflow" + REF="$GITHUB_REF" + REPO="$GITHUB_REPOSITORY" + fi + + # + # Now that the inputs are sorted, set the output of this step to these + # values so future jobs can refer to them. + # + echo ::set-output name=tests::$TESTS + echo ::set-output name=kernel::$KERNEL + echo ::set-output name=hostapd_version::$HOSTAPD_VERSION + echo ::set-output name=ell_ref::$ELL_REF + echo ::set-output name=repository::$REPO + echo ::set-output name=ref_branch::$REF + + - name: Cache UML Kernel + id: cache-uml-kernel + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/cache/um-linux-${{ steps.inputs.outputs.kernel }} + key: um-linux-${{ steps.inputs.outputs.kernel }}_ubuntu22 + + - name: Cache Hostapd + id: cache-hostapd + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/cache/hostapd_${{ steps.inputs.outputs.hostapd_version }} + ${{ github.workspace }}/cache/hostapd_cli_${{ steps.inputs.outputs.hostapd_version }} + key: hostapd_${{ steps.inputs.outputs.hostapd_version }}_ssl3 + + - name: Cache WpaSupplicant + id: cache-wpas + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/cache/wpa_supplicant_${{ steps.inputs.outputs.hostapd_version }} + ${{ github.workspace }}/cache/wpa_cli_${{ steps.inputs.outputs.hostapd_version }} + key: wpa_supplicant_${{ steps.inputs.outputs.hostapd_version }}_ssl3 + + - name: Checkout IWD + uses: actions/checkout@v3 + with: + path: iwd + repository: IWDTestBot/iwd + token: ${{ secrets.ACTION_TOKEN }} + + - name: Checkout ELL + uses: actions/checkout@v3 + with: + path: ell + repository: IWDTestBot/ell + ref: ${{ steps.inputs.outputs.ell_ref }} + + - name: Checkout CiBase + uses: actions/checkout@v3 + with: + repository: IWDTestBot/cibase + path: cibase + + - name: Checkout CI + uses: actions/checkout@v3 + with: + repository: IWDTestBot/iwd-ci-v2 + path: iwd-ci + + - name: Tar files + run: | + tar -cvf archive.tar \ + ${{ github.workspace }}/cache/um-linux-${{ steps.inputs.outputs.kernel }} \ + ${{ github.workspace }}/cache/hostapd_${{ steps.inputs.outputs.hostapd_version }} \ + ${{ github.workspace }}/cache/hostapd_cli_${{ steps.inputs.outputs.hostapd_version }} \ + ${{ github.workspace }}/cache/wpa_supplicant_${{ steps.inputs.outputs.hostapd_version }} \ + ${{ github.workspace }}/cache/wpa_cli_${{ steps.inputs.outputs.hostapd_version }} \ + iwd \ + ell \ + cibase \ + iwd-ci \ + cache + + - name: Upload artifacts + uses: actions/upload-artifact@v3 + with: + name: iwd-artifacts + path: | + archive.tar + + iwd-alpine-ci: + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: iwd-artifacts + + - name: Untar + run: tar -xf archive.tar + + - name: Modprobe pkcs8_key_parser + run: | + sudo modprobe pkcs8_key_parser + + - name: Alpine CI + uses: IWDTestBot/iwd-ci-v2@master + with: + ref_branch: ${{ needs.setup.outputs.ref_branch }} + repository: ${{ needs.setup.outputs.repository }} + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + ci: musl + + iwd-ci: + runs-on: ubuntu-22.04 + needs: setup + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + with: + name: iwd-artifacts + + - name: Untar + run: tar -xf archive.tar + + - name: Modprobe pkcs8_key_parser + run: | + sudo modprobe pkcs8_key_parser + echo ${{ needs.setup.outputs.ref_branch }} + echo ${{ needs.setup.outputs.repository }} + + - name: Run CI + uses: IWDTestBot/iwd-ci-v2@master + with: + ref_branch: ${{ needs.setup.outputs.ref_branch }} + repository: ${{ needs.setup.outputs.repository }} + tests: ${{ needs.setup.outputs.tests }} + kernel: ${{ needs.setup.outputs.kernel }} + hostapd_version: ${{ needs.setup.outputs.hostapd_version }} + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + ci: main + + - name: Upload Logs + if: always() + uses: actions/upload-artifact@v3 + with: + name: test-runner-logs + path: ${{ github.workspace }}/log diff --git a/.github/workflows/pw-to-pr-email.txt b/.github/workflows/pw-to-pr-email.txt new file mode 100644 index 000000000..0ad6d7659 --- /dev/null +++ b/.github/workflows/pw-to-pr-email.txt @@ -0,0 +1,16 @@ +This is an automated email and please do not reply to this email. + +Dear Submitter, + +Thank you for submitting the patches to the IWD mailing list. +While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository. + +----- Output ----- +{} + +Please resolve the issue and submit the patches again. + + +--- +Regards, +IWDTestBot diff --git a/.github/workflows/pw-to-pr.json b/.github/workflows/pw-to-pr.json new file mode 100644 index 000000000..b4491413c --- /dev/null +++ b/.github/workflows/pw-to-pr.json @@ -0,0 +1,14 @@ +{ + "email": { + "enable": true, + "server": "smtp.gmail.com", + "port": 587, + "user": "iwd.ci.bot@gmail.com", + "starttls": true, + "default-to": "prestwoj@gmail.com", + "only-maintainers": false, + "maintainers": [ + "prestwoj@gmail.com" + ] + } +} diff --git a/.github/workflows/schedule_work.yml b/.github/workflows/schedule_work.yml new file mode 100644 index 000000000..cfc14fba9 --- /dev/null +++ b/.github/workflows/schedule_work.yml @@ -0,0 +1,43 @@ +name: Sync Upstream +on: + schedule: + - cron: "*/15 * * * *" + workflow_dispatch: + +jobs: + repo-sync: + runs-on: ubuntu-latest + steps: + + - uses: actions/checkout@v2 + with: + persist-credentials: false + fetch-depth: 0 + + - name: Manage Repo + uses: IWDTestBot/action-manage-repo@master + with: + src_repo: "https://git.kernel.org/pub/scm/network/wireless/iwd.git" + src_branch: "master" + dest_branch: "master" + workflow_branch: "workflow" + github_token: ${{ secrets.GITHUB_TOKEN }} + + create_pr: + needs: repo-sync + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + + - name: Patchwork to PR + uses: IWDTestBot/action-patchwork-to-pr@master + with: + pw_key_str: "user" + github_token: ${{ secrets.ACTION_TOKEN }} + email_token: ${{ secrets.EMAIL_TOKEN }} + patchwork_token: ${{ secrets.PATCHWORK_TOKEN }} + config: https://raw.githubusercontent.com/IWDTestBot/iwd/workflow/.github/workflows/pw-to-pr.json + patchwork_id: "408" + email_message: https://raw.githubusercontent.com/IWDTestBot/iwd/workflow/.github/workflows/pw-to-pr-email.txt From f2b4359d59bcfcc0c859a340ef49b97547744331 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 24 Jun 2022 15:27:03 -0700 Subject: [PATCH 02/18] workflow: use newer commit for hostapd --- .github/workflows/ci.yml | 61 +++++++++++++++++++++++++++++++--------- 1 file changed, 47 insertions(+), 14 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e140ad8c..4bf5b1347 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ on: default: '5.16' hostapd_version: description: Hostapd and wpa_supplicant version - default: '2_10' + default: '09a281e52a25b5461c4b08d261f093181266a554' ell_ref: description: ELL reference default: refs/heads/workflow @@ -75,14 +75,14 @@ jobs: then TESTS=all KERNEL=5.16 - HOSTAPD_VERSION=2_10 + HOSTAPD_VERSION=09a281e52a25b5461c4b08d261f093181266a554 ELL_REF=${{ github.event.client_payload.ref }} REF=$ELL_REF REPO=${{ github.event.client_payload.repo }} else TESTS=all KERNEL=5.16 - HOSTAPD_VERSION=2_10 + HOSTAPD_VERSION=09a281e52a25b5461c4b08d261f093181266a554 ELL_REF="refs/heads/workflow" REF="$GITHUB_REF" REPO="$GITHUB_REPOSITORY" @@ -152,17 +152,25 @@ jobs: - name: Tar files run: | - tar -cvf archive.tar \ - ${{ github.workspace }}/cache/um-linux-${{ steps.inputs.outputs.kernel }} \ - ${{ github.workspace }}/cache/hostapd_${{ steps.inputs.outputs.hostapd_version }} \ - ${{ github.workspace }}/cache/hostapd_cli_${{ steps.inputs.outputs.hostapd_version }} \ - ${{ github.workspace }}/cache/wpa_supplicant_${{ steps.inputs.outputs.hostapd_version }} \ - ${{ github.workspace }}/cache/wpa_cli_${{ steps.inputs.outputs.hostapd_version }} \ - iwd \ - ell \ - cibase \ - iwd-ci \ - cache + FILES="iwd ell cibase iwd-ci cache" + + if [ "${{ steps.cache-uml-kernel.outputs.cache-hit }}" == 'true' ] + then + FILES+=" ${{ github.workspace }}/cache/um-linux-${{ steps.inputs.outputs.kernel }}" + fi + + if [ "${{ steps.cache-hostapd.outputs.cache-hit }}" == 'true' ] + then + FILES+=" ${{ github.workspace }}/cache/hostapd_${{ steps.inputs.outputs.hostapd_version }}" + FILES+=" ${{ github.workspace }}/cache/hostapd_cli_${{ steps.inputs.outputs.hostapd_version }}" + fi + if [ "${{ steps.cache-wpas.outputs.cache-hit }}" == 'true' ] + then + FILES+=" ${{ github.workspace }}/cache/wpa_supplicant_${{ steps.inputs.outputs.hostapd_version }}" + FILES+=" ${{ github.workspace }}/cache/wpa_cli_${{ steps.inputs.outputs.hostapd_version }}" + fi + + tar -cvf archive.tar $FILES - name: Upload artifacts uses: actions/upload-artifact@v3 @@ -209,6 +217,31 @@ jobs: - name: Untar run: tar -xf archive.tar + - name: Cache UML Kernel + id: cache-uml-kernel + uses: actions/cache@v3 + with: + path: ${{ github.workspace }}/cache/um-linux-${{ needs.setup.outputs.kernel }} + key: um-linux-${{ needs.setup.outputs.kernel }}_ubuntu22 + + - name: Cache Hostapd + id: cache-hostapd + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/cache/hostapd_${{ needs.setup.outputs.hostapd_version }} + ${{ github.workspace }}/cache/hostapd_cli_${{ needs.setup.outputs.hostapd_version }} + key: hostapd_${{ needs.setup.outputs.hostapd_version }}_ssl3 + + - name: Cache WpaSupplicant + id: cache-wpas + uses: actions/cache@v3 + with: + path: | + ${{ github.workspace }}/cache/wpa_supplicant_${{ needs.setup.outputs.hostapd_version }} + ${{ github.workspace }}/cache/wpa_cli_${{ needs.setup.outputs.hostapd_version }} + key: wpa_supplicant_${{ needs.setup.outputs.hostapd_version }}_ssl3 + - name: Modprobe pkcs8_key_parser run: | sudo modprobe pkcs8_key_parser From 9135fe39e7a377baf2cd22da8d257332482fefd8 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Wed, 7 Sep 2022 14:51:41 -0700 Subject: [PATCH 03/18] ci: remove cache/ from tar file list This is taken care of by the individual cache items and if none exist, tar fails. --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4bf5b1347..09bbb2961 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -152,7 +152,7 @@ jobs: - name: Tar files run: | - FILES="iwd ell cibase iwd-ci cache" + FILES="iwd ell cibase iwd-ci" if [ "${{ steps.cache-uml-kernel.outputs.cache-hit }}" == 'true' ] then From 9804929a8c3b2c375ff48eae3616e44af0698e99 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Wed, 14 Sep 2022 15:35:30 -0700 Subject: [PATCH 04/18] ci: use kernel 5.19 --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 09bbb2961..20b2e8419 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ on: default: all kernel: description: Kernel version - default: '5.16' + default: '5.19' hostapd_version: description: Hostapd and wpa_supplicant version default: '09a281e52a25b5461c4b08d261f093181266a554' @@ -74,14 +74,14 @@ jobs: elif [ ${{ github.event_name }} == 'repository_dispatch' ] then TESTS=all - KERNEL=5.16 + KERNEL=5.19 HOSTAPD_VERSION=09a281e52a25b5461c4b08d261f093181266a554 ELL_REF=${{ github.event.client_payload.ref }} REF=$ELL_REF REPO=${{ github.event.client_payload.repo }} else TESTS=all - KERNEL=5.16 + KERNEL=5.19 HOSTAPD_VERSION=09a281e52a25b5461c4b08d261f093181266a554 ELL_REF="refs/heads/workflow" REF="$GITHUB_REF" From 76a3159bf5c0453df9f6d2597caa8f1e05ecf771 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 14 Oct 2022 08:58:15 -0700 Subject: [PATCH 05/18] ci: use iwd-ci after renaming to remove -v2 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 20b2e8419..3f9d6981a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,7 +15,7 @@ name: IWD CI # as test-runner # * 'musl' uses an alpine docker image to test the build on musl-libc # -# Both CI's use the 'iwd-ci-v2' repo which calls into 'ci-docker'. The +# Both CI's use the 'iwd-ci' repo which calls into 'ci-docker'. The # 'ci-docker' action essentially re-implements the native Github docker # action but allows arbitrary options to be passed in (e.g. privileged or # mounting non-standard directories) @@ -147,7 +147,7 @@ jobs: - name: Checkout CI uses: actions/checkout@v3 with: - repository: IWDTestBot/iwd-ci-v2 + repository: IWDTestBot/iwd-ci path: iwd-ci - name: Tar files @@ -196,7 +196,7 @@ jobs: sudo modprobe pkcs8_key_parser - name: Alpine CI - uses: IWDTestBot/iwd-ci-v2@master + uses: IWDTestBot/iwd-ci@master with: ref_branch: ${{ needs.setup.outputs.ref_branch }} repository: ${{ needs.setup.outputs.repository }} @@ -249,7 +249,7 @@ jobs: echo ${{ needs.setup.outputs.repository }} - name: Run CI - uses: IWDTestBot/iwd-ci-v2@master + uses: IWDTestBot/iwd-ci@master with: ref_branch: ${{ needs.setup.outputs.ref_branch }} repository: ${{ needs.setup.outputs.repository }} From d20a0bf3203e627e2b359275cd3cda530d2f4c78 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 14 Oct 2022 10:18:25 -0700 Subject: [PATCH 06/18] ci: remove set-output use, now deprecated --- .github/workflows/ci.yml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3f9d6981a..393341c27 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -92,12 +92,12 @@ jobs: # Now that the inputs are sorted, set the output of this step to these # values so future jobs can refer to them. # - echo ::set-output name=tests::$TESTS - echo ::set-output name=kernel::$KERNEL - echo ::set-output name=hostapd_version::$HOSTAPD_VERSION - echo ::set-output name=ell_ref::$ELL_REF - echo ::set-output name=repository::$REPO - echo ::set-output name=ref_branch::$REF + echo "tests=$TESTS" >> $GITHUB_OUTPUT + echo "kernel=$KERNEL" >> $GITHUB_OUTPUT + echo "hostapd_version=$HOSTAPD_VERSION" >> $GITHUB_OUTPUT + echo "ell_ref=$ELL_REF" >> $GITHUB_OUTPUT + echo "repository=$REPO" >> $GITHUB_OUTPUT + echo "ref_branch=$REF" >> $GITHUB_OUTPUT - name: Cache UML Kernel id: cache-uml-kernel From c9d118269975843771b66787ea886c9203983b88 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Thu, 7 Nov 2024 06:12:51 -0800 Subject: [PATCH 07/18] Update kernel to 6.2 and hostapd/wpa_s to 2.11 --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 393341c27..993ce662d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,10 +30,10 @@ on: default: all kernel: description: Kernel version - default: '5.19' + default: '6.2' hostapd_version: description: Hostapd and wpa_supplicant version - default: '09a281e52a25b5461c4b08d261f093181266a554' + default: 'hostapd_2_11' ell_ref: description: ELL reference default: refs/heads/workflow From f640860d588a45be57cb803bef48a3196e9e9178 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Thu, 13 Feb 2025 08:18:29 -0800 Subject: [PATCH 08/18] Update upload/download-artifact to v4 --- .github/workflows/ci.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 993ce662d..a9582eb14 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -173,7 +173,7 @@ jobs: tar -cvf archive.tar $FILES - name: Upload artifacts - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: iwd-artifacts path: | @@ -184,7 +184,7 @@ jobs: needs: setup steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: iwd-artifacts @@ -210,7 +210,7 @@ jobs: needs: setup steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: iwd-artifacts @@ -263,7 +263,7 @@ jobs: - name: Upload Logs if: always() - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: test-runner-logs path: ${{ github.workspace }}/log From 244903b960945f559b0527ba7cb7417978606089 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Fri, 18 Apr 2025 12:03:43 -0700 Subject: [PATCH 09/18] Add coverity Github action --- .github/workflows/coverity.yml | 86 ++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 .github/workflows/coverity.yml diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 000000000..91f9073d3 --- /dev/null +++ b/.github/workflows/coverity.yml @@ -0,0 +1,86 @@ +name: Coverity Scan and Submit +description: Runs a coverity scan, then sends results to the cloud +on: + schedule: + - cron: "0 0 * * *" + workflow_dispatch: + +jobs: + scan-and-submit: + runs-on: ubuntu-22.04 + steps: + - name: Lookup latest tool + id: cache-lookup + run: | + hash=$(curl https://scan.coverity.com/download/cxx/linux64 \ + --data "token=${{ secrets.COVERITY_IWD_TOKEN }}&project=IWD&md5=1"); + echo "hash=${hash}" >> $GITHUB_OUTPUT + + - name: Get cached coverity tool + id: build-cache + uses: actions/cache@v4 + with: + path: ${{ github.workspace }}/cov-analysis + key: cov-build-cxx-linux64-${{ steps.cache-lookup.outputs.hash }} + + - name: Download Coverity Build Tool + if: steps.build-cache.outputs.cache-hit != 'true' + run: | + curl https://scan.coverity.com/download/cxx/linux64 \ + --no-progress-meter \ + --output cov-analysis.tar.gz \ + --data "token=${{ secrets.COVERITY_IWD_TOKEN }}&project=IWD" + shell: bash + working-directory: ${{ github.workspace }} + + - if: steps.build-cache.outputs.cache-hit != 'true' + run: mkdir cov-analysis + shell: bash + working-directory: ${{ github.workspace }} + + - if: steps.build-cache.outputs.cache-hit != 'true' + run: tar -xzf cov-analysis.tar.gz --strip 1 -C cov-analysis + shell: bash + working-directory: ${{ github.workspace }} + + - name: Checkout IWD + uses: actions/checkout@v3 + with: + path: ${{ github.workspace }}/iwd + repository: IWDTestBot/iwd + token: ${{ secrets.ACTION_TOKEN }} + + - name: Checkout ELL + uses: actions/checkout@v3 + with: + path: ${{ github.workspace }}/ell + repository: IWDTestBot/ell + token: ${{ secrets.ACTION_TOKEN }} + + - name: Configure IWD + run: | + cd ${{ github.workspace }}/iwd + ./bootstrap-configure --disable-manual-pages + + - name: Build with cov-build + run: | + export PATH="${{ github.workspace }}/cov-analysis/bin:${PATH}" + cov-build --dir cov-int make -j4 + shell: bash + working-directory: ${{ github.workspace }}/iwd + + - name: Tar results + run: tar -czvf cov-int.tgz cov-int + shell: bash + working-directory: ${{ github.workspace }}/iwd + + - name: Submit results to Coverity Scan + if: ${{ ! inputs.dry_run }} + run: | + curl \ + --form token="${{ secrets.COVERITY_IWD_TOKEN }}" \ + --form email="iwd.ci.bot@gmail.com" \ + --form file=@cov-int.tgz \ + "https://scan.coverity.com/builds?project=IWD" + shell: bash + working-directory: ${{ github.workspace }}/iwd From 25a40e4407890f23bf4844efd76b95810e1af714 Mon Sep 17 00:00:00 2001 From: James Prestwood Date: Mon, 18 Aug 2025 07:55:14 -0700 Subject: [PATCH 10/18] Fix hostap branch name --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9582eb14..51153d6dd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -33,7 +33,7 @@ on: default: '6.2' hostapd_version: description: Hostapd and wpa_supplicant version - default: 'hostapd_2_11' + default: 'hostap_2_11' ell_ref: description: ELL reference default: refs/heads/workflow From b7eedf9d501f34b4a459129e855ab9cd7f71c318 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:22 +0100 Subject: [PATCH 11/18] util: add scan_freq_set_size function Return the number of freqs in a scan_freq_set, useful for knowing how big the set is. --- src/util.c | 11 +++++++++++ src/util.h | 1 + 2 files changed, 12 insertions(+) diff --git a/src/util.c b/src/util.c index 65b97c8eb..1e1dfc150 100644 --- a/src/util.c +++ b/src/util.c @@ -637,6 +637,17 @@ struct scan_freq_set *scan_freq_set_clone(const struct scan_freq_set *set, return new; } +uint32_t scan_freq_set_size(struct scan_freq_set *freqs) +{ + uint32_t size = 0; + + size += __builtin_popcount(freqs->channels_2ghz); + size += l_uintset_size(freqs->channels_5ghz); + size += l_uintset_size(freqs->channels_6ghz); + + return size; +} + /* First 64 entries calculated by 1 / pow(n, 0.3) for n >= 1 */ static const double rankmod_table[] = { 1.0000000000, 0.8122523964, 0.7192230933, 0.6597539554, diff --git a/src/util.h b/src/util.h index 8aef2985b..7c024c791 100644 --- a/src/util.h +++ b/src/util.h @@ -138,6 +138,7 @@ uint32_t *scan_freq_set_to_fixed_array(const struct scan_freq_set *set, size_t *len_out); struct scan_freq_set *scan_freq_set_clone(const struct scan_freq_set *set, uint32_t band_mask); +uint32_t scan_freq_set_size(struct scan_freq_set *freqs); DEFINE_CLEANUP_FUNC(scan_freq_set_free); From a9e03be5a927498c16beff338bcbf0ba367e9ffe Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:23 +0100 Subject: [PATCH 12/18] station: improve scan_freqs_order channel subsets Splits the scan frequencies into more subsets that have been ordered such that the more common frequencies appear first, and the more uncommon frequencies last. Non-DFS channels are also added to the earlier subsets to prioritize fast-scanning frequencies. This approach allows iwd to scan the frequencies with the statistically highest chance for BSSes first, resulting in shorter scan times until a good BSS is found. In future patches this will also be used when roaming. --- src/station.c | 137 ++++++++++++++++++++++++++++++++------------------ 1 file changed, 88 insertions(+), 49 deletions(-) diff --git a/src/station.c b/src/station.c index f8069d897..15937fbe1 100644 --- a/src/station.c +++ b/src/station.c @@ -125,7 +125,7 @@ struct station { struct l_queue *roam_bss_list; /* Frequencies split into subsets by priority */ - struct scan_freq_set *scan_freqs_order[3]; + struct scan_freq_set *scan_freqs_order[5]; unsigned int dbus_scan_subset_idx; uint32_t wiphy_watch; @@ -4500,8 +4500,7 @@ static bool station_dbus_scan_results(int err, struct l_queue *bss_list, return false; } - last_subset = next_idx >= L_ARRAY_SIZE(station->scan_freqs_order) || - station->scan_freqs_order[next_idx] == NULL; + last_subset = next_idx >= L_ARRAY_SIZE(station->scan_freqs_order); station->dbus_scan_subset_idx = next_idx; station_set_scan_results(station, bss_list, freqs, false); @@ -4516,6 +4515,15 @@ static bool station_dbus_scan_subset(struct station *station) { unsigned int idx = station->dbus_scan_subset_idx; + /* Find the next non-empty subset */ + while (idx < L_ARRAY_SIZE(station->scan_freqs_order) && + scan_freq_set_isempty(station->scan_freqs_order[idx])) + idx++; + station->dbus_scan_subset_idx = idx; + + if (idx >= L_ARRAY_SIZE(station->scan_freqs_order)) + return false; + station->dbus_scan_id = station_scan_trigger(station, station->scan_freqs_order[idx], station_dbus_scan_triggered, @@ -5046,67 +5054,101 @@ int station_hide_network(struct station *station, struct network *network) return 0; } -static void station_add_2_4ghz_freq(uint32_t freq, void *user_data) -{ - struct scan_freq_set *set = user_data; - - /* exclude social channels added in initial scan request */ - if (freq < 3000 && freq != 2412 && freq != 2437 && freq != 2462) - scan_freq_set_add(set, freq); -} - static void station_fill_scan_freq_subsets(struct station *station) { const struct scan_freq_set *supported = wiphy_get_supported_freqs(station->wiphy); unsigned int subset_idx = 0; - /* - * Scan the 2.4GHz "social channels" first, 5GHz second, if supported, - * all other 2.4GHz channels last. To be refined as needed. - */ + station->scan_freqs_order[subset_idx] = scan_freq_set_new(); + + /* Subset 0: 2.4GHz "social channels" and low 5GHz non-DFS channels */ if (allowed_bands & BAND_FREQ_2_4_GHZ) { - station->scan_freqs_order[subset_idx] = scan_freq_set_new(); + /* Channels 1, 6, 11 */ scan_freq_set_add(station->scan_freqs_order[subset_idx], 2412); scan_freq_set_add(station->scan_freqs_order[subset_idx], 2437); scan_freq_set_add(station->scan_freqs_order[subset_idx], 2462); - subset_idx++; } - /* - * TODO: It may might sense to split up 5 and 6ghz into separate subsets - * since the channel set is so large. - */ - if (allowed_bands & (BAND_FREQ_5_GHZ | BAND_FREQ_6_GHZ)) { - uint32_t mask = allowed_bands & - (BAND_FREQ_5_GHZ | BAND_FREQ_6_GHZ); - struct scan_freq_set *set = scan_freq_set_clone(supported, - mask); - - /* 5/6ghz didn't add any frequencies */ - if (scan_freq_set_isempty(set)) { - scan_freq_set_free(set); - } else - station->scan_freqs_order[subset_idx++] = set; - } + if (allowed_bands & BAND_FREQ_5_GHZ) + /* Channels 32 - 48 */ + for (int i = 5160; i <= 5240; i+=20) + scan_freq_set_add(station->scan_freqs_order[subset_idx], i); + + scan_freq_set_constrain(station->scan_freqs_order[subset_idx], supported); + station->scan_freqs_order[++subset_idx] = scan_freq_set_new(); + + /* Subset 1: Remaining common 2.4GHz channels and high 5GHz non-DFS channels */ + if (allowed_bands & BAND_FREQ_2_4_GHZ) + /* Channels 2 - 10, except 6 */ + for (int i = 2417; i < 2462; i+=5) + if (i != 2437) + scan_freq_set_add(station->scan_freqs_order[subset_idx], i); + + if (allowed_bands & BAND_FREQ_5_GHZ) + /* Channels 149 - 177 */ + for (int i = 5745; i <= 5885; i+=20) + scan_freq_set_add(station->scan_freqs_order[subset_idx], i); - /* Add remaining 2.4ghz channels to subset */ + scan_freq_set_constrain(station->scan_freqs_order[subset_idx], supported); + station->scan_freqs_order[++subset_idx] = scan_freq_set_new(); + + /* Subset 2: Uncommon 2.4GHz channels and 5GHz DFS channels */ if (allowed_bands & BAND_FREQ_2_4_GHZ) { - station->scan_freqs_order[subset_idx] = scan_freq_set_new(); - scan_freq_set_foreach(supported, station_add_2_4ghz_freq, - station->scan_freqs_order[subset_idx]); + /* Channels 12 - 14 */ + scan_freq_set_add(station->scan_freqs_order[subset_idx], 2467); + scan_freq_set_add(station->scan_freqs_order[subset_idx], 2472); + scan_freq_set_add(station->scan_freqs_order[subset_idx], 2484); + } + + if (allowed_bands & BAND_FREQ_5_GHZ) { + /* Channels 52 - 68 */ + for (int i = 5260; i <= 5340; i+=20) + scan_freq_set_add(station->scan_freqs_order[subset_idx], i); + + /* Channels 96 - 144 */ + for (int i = 5480; i <= 5720; i+=20) + scan_freq_set_add(station->scan_freqs_order[subset_idx], i); + } + + scan_freq_set_constrain(station->scan_freqs_order[subset_idx], supported); + station->scan_freqs_order[++subset_idx] = scan_freq_set_new(); + + /* Subset 3: 6GHz channels */ + if (allowed_bands & BAND_FREQ_6_GHZ) { + struct scan_freq_set *set = scan_freq_set_clone(supported, BAND_FREQ_6_GHZ); + + if (!scan_freq_set_isempty(set)) + scan_freq_set_merge(station->scan_freqs_order[subset_idx], set); + + scan_freq_set_free(set); } + scan_freq_set_constrain(station->scan_freqs_order[subset_idx], supported); + station->scan_freqs_order[++subset_idx] = scan_freq_set_clone(supported, allowed_bands); + + /* All channels that are both supported and allowed should be in the subsets, + * if this is not the case then some new channel has been added that we are + * not tracking, put it in the last subset to make sure it's scanned */ + for (unsigned int i = 0; i < L_ARRAY_SIZE(station->scan_freqs_order) - 1; i++) + scan_freq_set_subtract(station->scan_freqs_order[subset_idx], + station->scan_freqs_order[i]); + + if (!scan_freq_set_isempty(station->scan_freqs_order[subset_idx])) + l_warn("Final subset is not empty"); + /* - * This has the unintended consequence of allowing DBus scans to - * scan the entire spectrum rather than cause IWD to be completely - * non-functional. Rather than prevent DBus scans from working at all - * print a warning here. + * Loop through all subsets to see that there's at least one non-empty + * subset, otherwise iwd will not function as expected and the user + * should be warned. */ - if (station->scan_freqs_order[0] == NULL) + for (subset_idx = 0; subset_idx < L_ARRAY_SIZE(station->scan_freqs_order); subset_idx++) + if (!scan_freq_set_isempty(station->scan_freqs_order[subset_idx])) + break; + + if (subset_idx == L_ARRAY_SIZE(station->scan_freqs_order)) l_warn("All supported bands were disabled by user! IWD will not" " function as expected"); - } static void station_wiphy_watch(struct wiphy *wiphy, @@ -5273,11 +5315,8 @@ static void station_free(struct station *station) l_queue_destroy(station->anqp_pending, remove_anqp); - scan_freq_set_free(station->scan_freqs_order[0]); - scan_freq_set_free(station->scan_freqs_order[1]); - - if (station->scan_freqs_order[2]) - scan_freq_set_free(station->scan_freqs_order[2]); + for (uint8_t i = 0; i < L_ARRAY_SIZE(station->scan_freqs_order); i++) + scan_freq_set_free(station->scan_freqs_order[i]); wiphy_state_watch_remove(station->wiphy, station->wiphy_watch); From 3c36a27a77a90697e484943af33865ae3af265de Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:24 +0100 Subject: [PATCH 13/18] station: improve roam scan strategy When iwd decides to roam, it scans either neighbor freqs or known freqs (if neighbors are not available). If it fails to roam after getting the scan results, it scans ALL freqs. There's a high chance that both neighbor and/or known scans fail to roam and we end up scanning all freqs. This is very slow and if you're already moving away from the current BSS, there's a high chance you will lose connection completely before the scan is finished. Instead of scanning all freqs at once, use the already-defined subsets to optimize the scans. The subsets contain a handful of freqs each and are ordered to increase the chance of finding a good BSS early. In order to not scan the same freq multiple times, use a list (scanned_freqs) to keep track of which freqs have been scanned in the current roam attempt. When a roam scan is triggered, add the most prioritized freqs to the list of freqs that should be scanned. The order of priority is: 1. Neighbor freqs 2. Known freqs 3. Subsets, starting with index 0 and incrementing if the subset is exhausted A freq is only added to the scan if it has not yet been scanned in the current roam attempt. An exception to this are neighbor freqs. They have a higher chance of containing good BSSes, so they're scanned every 3rd scan (defined by STATION_SCANS_BEFORE_NEIGHBOR_SCAN). This approach results in more scans, but fewer freqs per scan, leading to shorter delays between scan results. It also avoids scanning the same freqs back-to-back, which is generally not very useful. In combination with the subset ordering, this increases the chance of finding a good BSS early and results in better roaming performance. --- src/station.c | 250 ++++++++++++++++++++++++-------------------------- 1 file changed, 118 insertions(+), 132 deletions(-) diff --git a/src/station.c b/src/station.c index 15937fbe1..05530813d 100644 --- a/src/station.c +++ b/src/station.c @@ -68,6 +68,8 @@ #define STATION_RECENT_NETWORK_LIMIT 5 #define STATION_RECENT_FREQS_LIMIT 5 +#define STATION_MAX_SCAN_FREQS 10 +#define STATION_SCANS_BEFORE_NEIGHBOR_SCAN 2 static struct l_queue *station_list; static uint32_t netdev_watch; @@ -123,6 +125,9 @@ struct station { /* Set of frequencies to scan first when attempting a roam */ struct scan_freq_set *roam_freqs; struct l_queue *roam_bss_list; + struct scan_freq_set *scan_freqs; + struct scan_freq_set *scanned_freqs; + uint8_t roam_scans_since_neighbor_scan; /* Frequencies split into subsets by priority */ struct scan_freq_set *scan_freqs_order[5]; @@ -141,7 +146,6 @@ struct station { struct handshake_state *hs; bool preparing_roam : 1; - bool roam_scan_full : 1; bool signal_low : 1; bool ap_directed_roaming : 1; bool scanning : 1; @@ -1940,7 +1944,6 @@ static void station_roam_state_clear(struct station *station) l_timeout_remove(station->roam_trigger_timeout); station->roam_trigger_timeout = NULL; station->preparing_roam = false; - station->roam_scan_full = false; station->signal_low = false; station->netconfig_after_roam = false; station->last_roam_scan = 0; @@ -2205,27 +2208,6 @@ static void parse_neighbor_report(struct station *station, } } -static void station_early_neighbor_report_cb(struct netdev *netdev, int err, - const uint8_t *reports, - size_t reports_len, - void *user_data) -{ - struct station *station = user_data; - - if (err == -ENODEV) - return; - - l_debug("ifindex: %u, error: %d(%s)", - netdev_get_ifindex(station->netdev), - err, err < 0 ? strerror(-err) : ""); - - if (!reports || err) - return; - - parse_neighbor_report(station, reports, reports_len, - &station->roam_freqs); -} - static bool station_try_next_bss(struct station *station) { struct scan_bss *next; @@ -2360,9 +2342,32 @@ static bool netconfig_after_roam(struct station *station) return true; } +static void station_neighbor_report_cb(struct netdev *netdev, int err, + const uint8_t *reports, + size_t reports_len, void *user_data) +{ + struct station *station = user_data; + + if (err == -ENODEV) + return; + + l_debug("ifindex: %u, error: %d(%s)", + netdev_get_ifindex(station->netdev), + err, err < 0 ? strerror(-err) : ""); + + if (!reports || err) { + l_debug("no neighbor report results"); + return; + } + + parse_neighbor_report(station, reports, reports_len, &station->roam_freqs); +} + static void station_roamed(struct station *station) { - station->roam_scan_full = false; + scan_freq_set_free(station->scanned_freqs); + station->scanned_freqs = scan_freq_set_new(); + station->roam_scans_since_neighbor_scan = STATION_SCANS_BEFORE_NEIGHBOR_SCAN; /* * Schedule another roaming attempt in case the signal continues to @@ -2382,7 +2387,7 @@ static void station_roamed(struct station *station) if (station->connected_bss->cap_rm_neighbor_report) { if (netdev_neighbor_report_req(station->netdev, - station_early_neighbor_report_cb) < 0) + station_neighbor_report_cb) < 0) l_warn("Could not request neighbor report"); } @@ -2404,8 +2409,10 @@ static void station_roam_retry(struct station *station) * time. */ station->preparing_roam = false; - station->roam_scan_full = false; station->ap_directed_roaming = false; + scan_freq_set_free(station->scanned_freqs); + station->scanned_freqs = scan_freq_set_new(); + station->roam_scans_since_neighbor_scan = STATION_SCANS_BEFORE_NEIGHBOR_SCAN; if (station->roam_freqs) { scan_freq_set_free(station->roam_freqs); @@ -2416,6 +2423,8 @@ static void station_roam_retry(struct station *station) station_roam_timeout_rearm(station, roam_retry_interval); } +static void station_start_roam(struct station *station); + static void station_roam_failed(struct station *station) { l_debug("%u", netdev_get_ifindex(station->netdev)); @@ -2438,39 +2447,22 @@ static void station_roam_failed(struct station *station) } /* - * We were told by the AP to roam, but failed. Try ourselves or - * wait for the AP to tell us to roam again - */ - if (station->ap_directed_roaming) { - /* - * The candidate list from the AP (or neighbor report) found - * no BSS's. Force a full scan - */ - if (!station->roam_scan_full) - goto full_scan; - - goto delayed_retry; - } - - /* - * If we tried a limited scan, failed and the signal is still low, - * repeat with a full scan right away + * Keep trying to roam if the signal is still low, or we were told by the AP + * to roam but failed. */ - if (station->signal_low && !station->roam_scan_full) { + if (station->signal_low || station->ap_directed_roaming) { /* * Since we're re-using roam_scan_id, explicitly cancel * the scan here, so that the destroy callback is not called * after the return of this function */ -full_scan: scan_cancel(netdev_get_wdev_id(station->netdev), station->roam_scan_id); - if (!station_roam_scan(station, NULL)) - return; + station_start_roam(station); + return; } -delayed_retry: station_roam_retry(station); } @@ -3057,7 +3049,6 @@ static int station_roam_scan(struct station *station, } if (!freq_set) { - station->roam_scan_full = true; params.freqs = allowed; station_debug_event(station, "full-roam-scan"); } else @@ -3080,108 +3071,103 @@ static int station_roam_scan(struct station *station, return 0; } -static int station_roam_scan_known_freqs(struct station *station) +static void station_filter_roam_scan_freq(uint32_t freq, void *user_data) { - const struct network_info *info = network_get_info( - station->connected_network); - struct scan_freq_set *freqs = network_info_get_roam_frequencies(info, - station->connected_bss->frequency, - STATION_RECENT_FREQS_LIMIT); - int r = -ENODATA; - - if (!freqs) - return r; + struct station *station = user_data; - if (!wiphy_constrain_freq_set(station->wiphy, freqs)) - goto free_set; + if (scan_freq_set_size(station->scan_freqs) >= STATION_MAX_SCAN_FREQS) + return; - r = station_roam_scan(station, freqs); + /* Skip freq if already scanned */ + if (scan_freq_set_contains(station->scanned_freqs, freq)) + return; -free_set: - scan_freq_set_free(freqs); - return r; + scan_freq_set_add(station->scan_freqs, freq); + scan_freq_set_add(station->scanned_freqs, freq); } -static void station_neighbor_report_cb(struct netdev *netdev, int err, - const uint8_t *reports, - size_t reports_len, void *user_data) +static void station_populate_roam_scan_freqs(struct station *station) { - struct station *station = user_data; - struct scan_freq_set *freq_set; - int r; + struct scan_freq_set *tmp; + const struct network_info *info; - if (err == -ENODEV) - return; + station->scan_freqs = scan_freq_set_new(); - l_debug("ifindex: %u, error: %d(%s)", - netdev_get_ifindex(station->netdev), - err, err < 0 ? strerror(-err) : ""); + /* Add current frequency, always scan this to get updated data for the + * current BSS */ + scan_freq_set_add(station->scan_freqs, station->connected_bss->frequency); + scan_freq_set_add(station->scanned_freqs, station->connected_bss->frequency); - /* - * Check if we're still attempting to roam -- if dbus Disconnect - * had been called in the meantime we just abort the attempt. - */ - if (!station->preparing_roam || err == -ENODEV) + /* Add neighbor frequencies */ + if (station->roam_scans_since_neighbor_scan >= + STATION_SCANS_BEFORE_NEIGHBOR_SCAN && station->roam_freqs) { + station->roam_scans_since_neighbor_scan = 0; + scan_freq_set_merge(station->scan_freqs, station->roam_freqs); + scan_freq_set_merge(station->scanned_freqs, station->roam_freqs); return; + } + station->roam_scans_since_neighbor_scan++; - if (!reports || err) { - r = station_roam_scan_known_freqs(station); - - if (r == -ENODATA) - l_debug("no neighbor report results or known freqs"); - - if (r < 0) - station_roam_failed(station); - + /* Add known frequencies */ + info = network_get_info(station->connected_network); + tmp = network_info_get_roam_frequencies(info, + station->connected_bss->frequency, + STATION_RECENT_FREQS_LIMIT); + scan_freq_set_foreach(tmp, station_filter_roam_scan_freq, station); + scan_freq_set_free(tmp); + if (scan_freq_set_size(station->scan_freqs) >= STATION_MAX_SCAN_FREQS) { return; } - parse_neighbor_report(station, reports, reports_len, &freq_set); + /* Add frequencies based on the prioritized subsets */ + for (uint8_t i = 0; i < L_ARRAY_SIZE(station->scan_freqs_order); i++) { + scan_freq_set_foreach(station->scan_freqs_order[i], station_filter_roam_scan_freq, station); + if (scan_freq_set_size(station->scan_freqs) >= STATION_MAX_SCAN_FREQS) { + return; + } + } - r = station_roam_scan(station, freq_set); + if (scan_freq_set_size(station->scan_freqs) <= STATION_MAX_SCAN_FREQS) { + /* All freqs have been scanned after this, so empty the list of scanned + * freqs to restart */ + scan_freq_set_free(station->scanned_freqs); + station->scanned_freqs = scan_freq_set_new(); - if (freq_set) - scan_freq_set_free(freq_set); + /* At this point we've gone through all frequencies, which is equivalent to + * a full scan */ + station_debug_event(station, "full-roam-scan"); - if (r < 0) - station_roam_failed(station); + /* If we were told by the AP to roam, we've now made the best possible + * effort to do so, clear the flag to stop trying to roam */ + station->ap_directed_roaming = false; + } } static void station_start_roam(struct station *station) { - int r; - station->preparing_roam = true; /* - * If current BSS supports Neighbor Reports, narrow the scan down - * to channels occupied by known neighbors in the ESS. If no neighbor - * report was obtained upon connection, request one now. This isn't - * 100% reliable as the neighbor lists are not required to be - * complete or current. It is likely still better than doing a - * full scan. 10.11.10.1: "A neighbor report may not be exhaustive - * either by choice, or due to the fact that there may be neighbor - * APs not known to the AP." + * If no neighbor report was obtained upon connection, request one now if BSS + * supports it. This isn't 100% reliable as the neighbor lists are not + * required to be complete or current. + * 10.11.10.1: "A neighbor report may not be exhaustive either by choice, or + * due to the fact that there may be neighbor APs not known to the AP." + * + * Continue roaming while waiting for the neighbor report, the neighbors will + * be added to the roam scan when/if they're available. */ - if (station->roam_freqs) { - if (station_roam_scan(station, station->roam_freqs) == 0) { - l_debug("Using cached neighbor report for roam"); - return; - } - } else if (station->connected_bss->cap_rm_neighbor_report) { + if (!station->roam_freqs && station->connected_bss->cap_rm_neighbor_report) { if (netdev_neighbor_report_req(station->netdev, station_neighbor_report_cb) == 0) { l_debug("Requesting neighbor report for roam"); - return; } } - r = station_roam_scan_known_freqs(station); - if (r == -ENODATA) - l_debug("No neighbor report or known frequencies, roam failed"); - - if (r < 0) - station_roam_failed(station); + station_populate_roam_scan_freqs(station); + station_roam_scan(station, station->scan_freqs); + scan_freq_set_free(station->scan_freqs); + station->scan_freqs = NULL; } static bool station_cannot_roam(struct station *station) @@ -3395,20 +3381,12 @@ static void station_ap_directed_roam(struct station *station, l_debug("roam: AP sent a preferred candidate list"); station_neighbor_report_cb(station->netdev, 0, body + pos, body_len - pos, station); - } else { - if (station->connected_bss->cap_rm_neighbor_report) { - if (!netdev_neighbor_report_req(station->netdev, - station_neighbor_report_cb)) - return; - - l_warn("failed to request neighbor report!"); - } - - l_debug("full scan after BSS transition request"); - if (station_roam_scan(station, NULL) < 0) - station_roam_failed(station); } + /* Initiate roaming, any candidates will be scanned and roaming will continue + * until successful if ap_directed_roaming is set */ + station_start_roam(station); + return; format_error: @@ -3624,7 +3602,7 @@ static void station_connect_ok(struct station *station) */ if (station->connected_bss->cap_rm_neighbor_report) { if (netdev_neighbor_report_req(station->netdev, - station_early_neighbor_report_cb) < 0) + station_neighbor_report_cb) < 0) l_warn("Could not request neighbor report"); } @@ -5229,6 +5207,9 @@ static struct station *station_create(struct netdev *netdev) station->roam_bss_list = l_queue_new(); station->affinities = l_queue_new(); + station->scanned_freqs = scan_freq_set_new(); + station->roam_scans_since_neighbor_scan = STATION_SCANS_BEFORE_NEIGHBOR_SCAN; + return station; } @@ -5327,6 +5308,11 @@ static void station_free(struct station *station) l_queue_destroy(station->affinities, l_free); + scan_freq_set_free(station->scanned_freqs); + + if (station->scan_freqs) + scan_freq_set_free(station->scan_freqs); + l_free(station); } From da30ced87f7a68bd4e2fe4bc0d8569be7f6de992 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:25 +0100 Subject: [PATCH 14/18] auto-t: Increase event timeout for test_full_scan Reaching the full-roam-scan event now requires scanning several subsets which takes a bit longer, so the default timeout of 10 isn't long enough anymore. --- autotests/testAPRoam/bad_neighbor_report_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotests/testAPRoam/bad_neighbor_report_test.py b/autotests/testAPRoam/bad_neighbor_report_test.py index c1ec7e261..ab275e71b 100644 --- a/autotests/testAPRoam/bad_neighbor_report_test.py +++ b/autotests/testAPRoam/bad_neighbor_report_test.py @@ -49,7 +49,7 @@ def test_full_scan(self): self.device.wait_for_event("roam-scan-triggered") self.device.wait_for_event("no-roam-candidates") # IWD should then trigger a full scan - self.device.wait_for_event("full-roam-scan") + self.device.wait_for_event("full-roam-scan", timeout=30) self.device.wait_for_event("no-roam-candidates", timeout=30) # IWD should not trigger a roam again after the above 2 failures. From 84fb3c1ac1bc97ed76aef0af20dbec6dd2cf1d96 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:26 +0100 Subject: [PATCH 15/18] auto-t: Fix tests relying on full-roam-scan These tests can't be guaranteed to reach a full scan anymore, as iwd will find the BSS before that happens. Getting the roaming and connected events should be enough to pass these tests. --- autotests/testAPRoam/bad_neighbor_report_test.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/autotests/testAPRoam/bad_neighbor_report_test.py b/autotests/testAPRoam/bad_neighbor_report_test.py index ab275e71b..e361d37d9 100644 --- a/autotests/testAPRoam/bad_neighbor_report_test.py +++ b/autotests/testAPRoam/bad_neighbor_report_test.py @@ -71,8 +71,7 @@ def test_bad_candidate_list(self): ) self.device.wait_for_event("roam-scan-triggered") self.device.wait_for_event("no-roam-candidates") - # IWD should then trigger a full scan - self.device.wait_for_event("full-roam-scan") + # IWD should then start scanning until it finds a BSS self.device.wait_for_event("roaming", timeout=30) self.device.wait_for_event("connected") @@ -99,8 +98,7 @@ def test_bad_neighbor_report(self): # channel 11 which no AP is on. This should result in a limited scan # picking up no candidates. self.device.wait_for_event("no-roam-candidates", timeout=30) - # IWD should then trigger a full scan - self.device.wait_for_event("full-roam-scan") + # IWD should then start scanning until it finds a BSS self.device.wait_for_event("roaming", timeout=30) self.device.wait_for_event("connected") From 66f25905feb4f00ff4550847bc6d05002fcfad00 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:27 +0100 Subject: [PATCH 16/18] auto-t: Call default instead of reload in teardown This solves an issue where test_ignore_candidate_list_quirk() uses set_value() to set a vendor element telling the station that the candidate list should be ignored, and that config was never cleared in teardown. This resulted in subsequent tests ignoring the candidate list and doing a normal scan. This caused them to connect to a BSS other than the one in the candidate list, causing the tests to fail. When calling default(), it first resets all configs to default, then calls reload(). So we still get the reload() call as before, with the added benefit of also clearing the configs. This makes the tests pass. --- autotests/testAPRoam/bad_neighbor_report_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/autotests/testAPRoam/bad_neighbor_report_test.py b/autotests/testAPRoam/bad_neighbor_report_test.py index e361d37d9..c8e4e45a5 100644 --- a/autotests/testAPRoam/bad_neighbor_report_test.py +++ b/autotests/testAPRoam/bad_neighbor_report_test.py @@ -136,7 +136,7 @@ def tearDown(self): self.device = None for hapd in self.bss_hostapd: - hapd.reload() + hapd.default() @classmethod def setUpClass(cls): From 6288020f9d85e0f7fe0c1d17ae805a2e2cc127e4 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:28 +0100 Subject: [PATCH 17/18] auto-t: Fix test_ignore_candidate_list_quirk This test no longer triggers a full scan. Instead, we verify that it doesn't scan the bad candidate channel, which is verified by not getting "no-roam-candidates". Then we also verify that it roams to the other available BSS, which it should do immediately because that BSS is on a channel that's in the first subset of channels to be scanned. This means it should roam directly without getting "no-roam-candidates" in-between. To allow for this, add an optional "disallow" list of events to wait_for_event(). This functionality already exists in hostapd.py, so the same solution was copied to iwd.py. --- autotests/testAPRoam/bad_neighbor_report_test.py | 9 ++++----- autotests/util/iwd.py | 13 ++++++++----- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/autotests/testAPRoam/bad_neighbor_report_test.py b/autotests/testAPRoam/bad_neighbor_report_test.py index c8e4e45a5..5a8be6359 100644 --- a/autotests/testAPRoam/bad_neighbor_report_test.py +++ b/autotests/testAPRoam/bad_neighbor_report_test.py @@ -114,15 +114,14 @@ def test_ignore_candidate_list_quirk(self): self.initial_connection() - # Send with a candidate list (should be ignored) + # Send with a bad candidate list (should be ignored) self.bss_hostapd[0].send_bss_transition( self.device.address, [(self.bss_hostapd[1].bssid, "8f0000005105060603000000")] ) - # IWD should ignore the list and trigger a full scan since we have not - # set any neighbors - self.device.wait_for_event("full-roam-scan") - self.device.wait_for_event("roaming", timeout=30) + # IWD should ignore the list and not try to scan the bad channel, + # so we shouldn't get no-roam-candidates before we roam + self.device.wait_for_event("roaming", timeout=30, disallow=["no-roam-candidates"]) self.device.wait_for_event("connected") def setUp(self): diff --git a/autotests/util/iwd.py b/autotests/util/iwd.py index 37eb49434..c79afc833 100755 --- a/autotests/util/iwd.py +++ b/autotests/util/iwd.py @@ -297,8 +297,11 @@ def scan(self, frequencies): frequencies = dbus.Array([dbus.UInt16(f) for f in frequencies]) self._iface.Scan(frequencies) - def _poll_event(self, event): + def _poll_event(self, event, disallow): for idx, e in enumerate(self._events): + for d in disallow: + if d in e: + raise Exception('Event %s found while waiting for %s' % (d, event)) if event == e[0]: # Consume any older events self._events = self._events[:idx] @@ -309,8 +312,8 @@ def _poll_event(self, event): def clear_events(self): self._events = [] - def wait_for_event(self, event, timeout=10): - return ctx.non_block_wait(self._poll_event, timeout, event, + def wait_for_event(self, event, timeout=10, disallow=[]): + return ctx.non_block_wait(self._poll_event, timeout, event, disallow, exception=TimeoutError("waiting for event")) def event_ocurred(self, event): @@ -886,8 +889,8 @@ def roam(self, address): def debug_scan(self, frequencies): self._station_debug.scan(frequencies) - def wait_for_event(self, event, timeout=10): - self._station_debug.wait_for_event(event, timeout) + def wait_for_event(self, event, timeout=10, disallow=[]): + self._station_debug.wait_for_event(event, timeout, disallow) def clear_events(self): self._station_debug.clear_events() From 69214971d9af0635d34a1bd36924219fbacdf1e3 Mon Sep 17 00:00:00 2001 From: Alexander Ganslandt Date: Fri, 28 Nov 2025 12:54:29 +0100 Subject: [PATCH 18/18] auto-t: Disable MAC randomization for a few tests The scan subsets now contains both 2.4GHz and 5GHz channels, this seems to cause an issue when MAC randomization is used together with the simulated hardware. The AP(s) in these tests operate on 2.4GHz. From the logs it's clear that the station's probe request to the 2.4GHz AP does arrive, and the AP responds, but the station never processes the response. If the AP is changed to 5GHz then everything works fine. I believe this is because 2.4GHz is scanned first, then there's a MAC change, then 5GHz is scanned. For some reason, this causes the simulated hardware to miss the response from the first scan, possibly because the MAC address doesn't match. --- autotests/testAP-no-support/main.conf | 2 ++ autotests/testBasicServiceSet/main.conf | 2 ++ autotests/testDPP/main.conf | 2 ++ autotests/testDisconnectByAP/main.conf | 2 ++ 4 files changed, 8 insertions(+) create mode 100644 autotests/testAP-no-support/main.conf create mode 100644 autotests/testBasicServiceSet/main.conf create mode 100644 autotests/testDPP/main.conf create mode 100644 autotests/testDisconnectByAP/main.conf diff --git a/autotests/testAP-no-support/main.conf b/autotests/testAP-no-support/main.conf new file mode 100644 index 000000000..9452fb6bc --- /dev/null +++ b/autotests/testAP-no-support/main.conf @@ -0,0 +1,2 @@ +[Scan] +DisableMacAddressRandomization=true diff --git a/autotests/testBasicServiceSet/main.conf b/autotests/testBasicServiceSet/main.conf new file mode 100644 index 000000000..9452fb6bc --- /dev/null +++ b/autotests/testBasicServiceSet/main.conf @@ -0,0 +1,2 @@ +[Scan] +DisableMacAddressRandomization=true diff --git a/autotests/testDPP/main.conf b/autotests/testDPP/main.conf new file mode 100644 index 000000000..9452fb6bc --- /dev/null +++ b/autotests/testDPP/main.conf @@ -0,0 +1,2 @@ +[Scan] +DisableMacAddressRandomization=true diff --git a/autotests/testDisconnectByAP/main.conf b/autotests/testDisconnectByAP/main.conf new file mode 100644 index 000000000..9452fb6bc --- /dev/null +++ b/autotests/testDisconnectByAP/main.conf @@ -0,0 +1,2 @@ +[Scan] +DisableMacAddressRandomization=true