diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a9582eb1 --- /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: 'hostapd_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/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/handshake.c b/src/handshake.c index bee31beb..c469e6fa 100644 --- a/src/handshake.c +++ b/src/handshake.c @@ -1239,7 +1239,7 @@ static struct pmksa *handshake_state_steal_pmksa(struct handshake_state *s) s->have_pmksa = false; if (l_time_after(now, pmksa->expiration)) { - l_free(pmksa); + pmksa_cache_free(pmksa); pmksa = NULL; } @@ -1280,7 +1280,7 @@ void handshake_state_cache_pmksa(struct handshake_state *s) l_debug("Caching PMKSA for "MAC, MAC_STR(s->aa)); if (L_WARN_ON(pmksa_cache_put(pmksa) < 0)) - l_free(pmksa); + pmksa_cache_free(pmksa); } bool handshake_state_remove_pmksa(struct handshake_state *s) @@ -1294,7 +1294,7 @@ bool handshake_state_remove_pmksa(struct handshake_state *s) if (!pmksa) return false; - l_free(pmksa); + pmksa_cache_free(pmksa); return true; } diff --git a/src/netdev.c b/src/netdev.c index 2a6d94fc..3cdf27a6 100644 --- a/src/netdev.c +++ b/src/netdev.c @@ -1498,6 +1498,105 @@ static void netdev_setting_keys_failed(struct netdev_handshake_state *nhs, handshake_event(&nhs->super, HANDSHAKE_EVENT_SETTING_KEYS_FAILED, &err); } +static bool netdev_match_addr(const void *a, const void *b) +{ + const struct netdev *netdev = a; + const uint8_t *addr = b; + + return memcmp(netdev->addr, addr, ETH_ALEN) == 0; +} + +static struct netdev *netdev_find_by_address(const uint8_t *addr) +{ + return l_queue_find(netdev_list, netdev_match_addr, addr); +} + +static void netdev_pmksa_driver_add(const struct pmksa *pmksa) +{ + struct l_genl_msg *msg; + struct netdev *netdev = netdev_find_by_address(pmksa->spa); + uint32_t expiration = (uint32_t)pmksa->expiration; + + if (!netdev) + return; + + /* Only need to set the PMKSA into the kernel for fullmac drivers */ + if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) + return; + + l_debug("Adding PMKSA to kernel"); + + msg = l_genl_msg_new(NL80211_CMD_SET_PMKSA); + + l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); + l_genl_msg_append_attr(msg, NL80211_ATTR_PMKID, 16, pmksa->pmkid); + l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, pmksa->aa); + l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, + pmksa->ssid_len, pmksa->ssid); + l_genl_msg_append_attr(msg, NL80211_ATTR_PMK_LIFETIME, 4, &expiration); + l_genl_msg_append_attr(msg, NL80211_ATTR_PMK, + pmksa->pmk_len, pmksa->pmk); + + if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) + l_error("error sending SET_PMKSA"); +} + +static void netdev_pmksa_driver_remove(const struct pmksa *pmksa) +{ + struct l_genl_msg *msg; + struct netdev *netdev = netdev_find_by_address(pmksa->spa); + + if (!netdev) + return; + + /* Only need to set the PMKSA into the kernel for fullmac drivers */ + if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) + return; + + l_debug("Removing PMKSA from kernel"); + + msg = l_genl_msg_new(NL80211_CMD_DEL_PMKSA); + + l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); + l_genl_msg_append_attr(msg, NL80211_ATTR_PMKID, 16, pmksa->pmkid); + l_genl_msg_append_attr(msg, NL80211_ATTR_MAC, ETH_ALEN, pmksa->aa); + l_genl_msg_append_attr(msg, NL80211_ATTR_SSID, + pmksa->ssid_len, pmksa->ssid); + + if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) + l_error("error sending DEL_PMKSA"); +} + +static void netdev_flush_pmksa(struct netdev *netdev) +{ + struct l_genl_msg *msg; + + /* + * We only utilize the kernel's PMKSA cache for fullmac cards, + * so no need to flush if this is a softmac. + */ + if (wiphy_supports_cmds_auth_assoc(netdev->wiphy)) + return; + + msg = l_genl_msg_new(NL80211_CMD_FLUSH_PMKSA); + + l_genl_msg_append_attr(msg, NL80211_ATTR_IFINDEX, 4, &netdev->index); + + if (!l_genl_family_send(nl80211, msg, NULL, NULL, NULL)) + l_error("Failed to flush PMKSA for %u", netdev->index); +} + +static void netdev_pmksa_driver_flush(void) +{ + const struct l_queue_entry *e; + + for (e = l_queue_get_entries(netdev_list); e; e = e->next) { + struct netdev *netdev = e->data; + + netdev_flush_pmksa(netdev); + } +} + static void try_handshake_complete(struct netdev_handshake_state *nhs) { l_debug("ptk_installed: %u, gtk_installed: %u, igtk_installed: %u", @@ -3816,6 +3915,15 @@ static void netdev_cmd_set_cqm_cb(struct l_genl_msg *msg, void *user_data) static int netdev_cqm_rssi_update(struct netdev *netdev) { struct l_genl_msg *msg; + struct netdev_handshake_state *nhs = l_container_of(netdev->handshake, + struct netdev_handshake_state, super); + + /* + * Fullmac cards handle roaming in firmware, there is no need to set + * CQM thresholds + */ + if (nhs->type == CONNECTION_TYPE_FULLMAC) + return 0; l_debug(""); @@ -5504,23 +5612,18 @@ static void netdev_external_auth_event(struct l_genl_msg *msg, } if (action == NL80211_EXTERNAL_AUTH_ABORT) { - iwd_notice(IWD_NOTICE_CONNECT_INFO, "External Auth Aborted"); + l_warn("External Auth Aborted"); goto error; } - iwd_notice(IWD_NOTICE_CONNECT_INFO, - "External Auth to SSID: %s, bssid: "MAC, - util_ssid_to_utf8(ssid.iov_len, ssid.iov_base), - MAC_STR(bssid)); - if (hs->ssid_len != ssid.iov_len || memcmp(hs->ssid, ssid.iov_base, hs->ssid_len)) { - iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target SSID mismatch"); + l_warn("Target SSID mismatch"); goto error; } if (memcmp(hs->aa, bssid, ETH_ALEN)) { - iwd_notice(IWD_NOTICE_CONNECT_INFO, "Target BSSID mismatch"); + l_warn("Target BSSID mismatch"); goto error; } @@ -6529,6 +6632,16 @@ struct netdev *netdev_create_from_genl(struct l_genl_msg *msg, netdev_get_link(netdev); + /* + * Call the netdev-specific variant to flush only this devices PMKSA + * cache in the kernel. This will make IWD's cache and the kernel's + * cache consistent, i.e. no entries + * + * TODO: If we ever are storing PMKSA's on disk we would first need to + * flush, then add all the PMKSA entries at this time. + */ + netdev_flush_pmksa(netdev); + return netdev; } @@ -6644,6 +6757,10 @@ static int netdev_init(void) __ft_set_tx_frame_func(netdev_tx_ft_frame); + __pmksa_set_driver_callbacks(netdev_pmksa_driver_add, + netdev_pmksa_driver_remove, + netdev_pmksa_driver_flush); + unicast_watch = l_genl_add_unicast_watch(genl, NL80211_GENL_NAME, netdev_unicast_notify, NULL, NULL); diff --git a/src/pmksa.c b/src/pmksa.c index bb539b85..a50c8208 100644 --- a/src/pmksa.c +++ b/src/pmksa.c @@ -40,6 +40,9 @@ static uint64_t dot11RSNAConfigPMKLifetime = 43200ULL * L_USEC_PER_SEC; static uint32_t pmksa_cache_capacity = 255; +static pmksa_cache_add_func_t driver_add; +static pmksa_cache_remove_func_t driver_remove; +static pmksa_cache_flush_func_t driver_flush; struct min_heap { struct pmksa **data; @@ -142,7 +145,7 @@ int pmksa_cache_put(struct pmksa *pmksa) l_debug("Adding entry with PMKID: "PMKID, PMKID_STR(pmksa->pmkid)); if (cache.used == cache.capacity) { - l_free(cache.data[0]); + pmksa_cache_free(cache.data[0]); cache.data[0] = pmksa; __minheap_sift_down(cache.data, cache.used, 0, &ops); return 0; @@ -152,6 +155,9 @@ int pmksa_cache_put(struct pmksa *pmksa) __minheap_sift_up(cache.data, cache.used, &ops); cache.used += 1; + if (driver_add) + driver_add(pmksa); + return 0; } @@ -167,7 +173,7 @@ int pmksa_cache_expire(uint64_t cutoff) for (i = 0; i < used; i++) { if (cache.data[i]->expiration <= cutoff) { - l_free(cache.data[i]); + pmksa_cache_free(cache.data[i]); continue; } @@ -190,11 +196,30 @@ int pmksa_cache_flush(void) { uint32_t i; + /* + * The driver flush operation is done via a single kernel API call which + * is why below we use l_free instead of pmksa_cache_free as to not + * induce a DEL_PMKSA kernel call for each entry. + */ + if (driver_flush) + driver_flush(); + for (i = 0; i < cache.used; i++) l_free(cache.data[i]); memset(cache.data, 0, cache.capacity * sizeof(struct pmksa *)); cache.used = 0; + + return 0; +} + +int pmksa_cache_free(struct pmksa *pmksa) +{ + if (driver_remove) + driver_remove(pmksa); + + l_free(pmksa); + return 0; } @@ -217,6 +242,15 @@ void __pmksa_set_config(const struct l_settings *config) &pmksa_cache_capacity); } +void __pmksa_set_driver_callbacks(pmksa_cache_add_func_t add, + pmksa_cache_remove_func_t remove, + pmksa_cache_flush_func_t flush) +{ + driver_add = add; + driver_remove = remove; + driver_flush = flush; +} + static int pmksa_init(void) { cache.capacity = pmksa_cache_capacity; diff --git a/src/pmksa.h b/src/pmksa.h index 67879309..6a624504 100644 --- a/src/pmksa.h +++ b/src/pmksa.h @@ -32,6 +32,10 @@ struct pmksa { size_t pmk_len; }; +typedef void (*pmksa_cache_add_func_t)(const struct pmksa *pmksa); +typedef void (*pmksa_cache_remove_func_t)(const struct pmksa *pmksa); +typedef void (*pmksa_cache_flush_func_t)(void); + struct pmksa **__pmksa_cache_get_all(uint32_t *out_n_entries); struct pmksa *pmksa_cache_get(const uint8_t spa[static 6], @@ -41,6 +45,11 @@ struct pmksa *pmksa_cache_get(const uint8_t spa[static 6], int pmksa_cache_put(struct pmksa *pmksa); int pmksa_cache_expire(uint64_t cutoff); int pmksa_cache_flush(void); +int pmksa_cache_free(struct pmksa *pmksa); uint64_t pmksa_lifetime(void); void __pmksa_set_config(const struct l_settings *config); + +void __pmksa_set_driver_callbacks(pmksa_cache_add_func_t add, + pmksa_cache_remove_func_t remove, + pmksa_cache_flush_func_t flush);