diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..51153d6d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,269 @@ +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' 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: '6.2' + hostapd_version: + description: Hostapd and wpa_supplicant version + default: 'hostap_2_11' + 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.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.19 + HOSTAPD_VERSION=09a281e52a25b5461c4b08d261f093181266a554 + 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 "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 + 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 + path: iwd-ci + + - name: Tar files + run: | + FILES="iwd ell cibase iwd-ci" + + 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@v4 + 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@v4 + 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@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@v4 + with: + name: iwd-artifacts + + - 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 + echo ${{ needs.setup.outputs.ref_branch }} + echo ${{ needs.setup.outputs.repository }} + + - name: Run CI + uses: IWDTestBot/iwd-ci@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@v4 + with: + name: test-runner-logs + path: ${{ github.workspace }}/log diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml new file mode 100644 index 00000000..91f9073d --- /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 diff --git a/.github/workflows/pw-to-pr-email.txt b/.github/workflows/pw-to-pr-email.txt new file mode 100644 index 00000000..0ad6d765 --- /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 00000000..b4491413 --- /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 00000000..cfc14fba --- /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 diff --git a/src/station.c b/src/station.c index f8069d89..3eb1a71d 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,9 +125,12 @@ 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[3]; + struct scan_freq_set *scan_freqs_order[5]; unsigned int dbus_scan_subset_idx; uint32_t wiphy_watch; @@ -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,95 @@ 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); - - r = station_roam_scan(station, freq_set); - - if (freq_set) - scan_freq_set_free(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; + } + } - if (r < 0) - station_roam_failed(station); + 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(); + } } 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) @@ -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"); } @@ -4500,8 +4478,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 +4493,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 +5032,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, @@ -5187,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; } @@ -5273,11 +5296,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); @@ -5288,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); } diff --git a/src/util.c b/src/util.c index 65b97c8e..1e1dfc15 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 8aef2985..7c024c79 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);