adf_nbuf_t ol_rx_pn_check_base( struct ol_txrx_vdev_t *vdev, struct ol_txrx_peer_t *peer, unsigned tid, adf_nbuf_t msdu_list) { struct ol_txrx_pdev_t *pdev = vdev->pdev; union htt_rx_pn_t *last_pn; adf_nbuf_t out_list_head = NULL; adf_nbuf_t out_list_tail = NULL; adf_nbuf_t mpdu; int index; /* unicast vs. multicast */ int pn_len; void *rx_desc; int last_pn_valid; /* Make sure host pn check is not redundant */ if ((adf_os_atomic_read(&peer->fw_pn_check)) || (vdev->opmode == wlan_op_mode_ibss)) { return msdu_list; } /* First, check whether the PN check applies */ rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, msdu_list); adf_os_assert(htt_rx_msdu_has_wlan_mcast_flag(pdev->htt_pdev, rx_desc)); index = htt_rx_msdu_is_wlan_mcast(pdev->htt_pdev, rx_desc) ? txrx_sec_mcast : txrx_sec_ucast; pn_len = pdev->rx_pn[peer->security[index].sec_type].len; if (pn_len == 0) { return msdu_list; } last_pn_valid = peer->tids_last_pn_valid[tid]; last_pn = &peer->tids_last_pn[tid]; mpdu = msdu_list; while (mpdu) { adf_nbuf_t mpdu_tail, next_mpdu; union htt_rx_pn_t new_pn; int pn_is_replay = 0; rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, mpdu); /* * Find the last MSDU within this MPDU, and * the find the first MSDU within the next MPDU. */ ol_rx_mpdu_list_next(pdev, mpdu, &mpdu_tail, &next_mpdu); /* Don't check the PN replay for non-encrypted frames */ if (!htt_rx_mpdu_is_encrypted(pdev->htt_pdev, rx_desc)) { ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail); mpdu = next_mpdu; continue; } /* retrieve PN from rx descriptor */ htt_rx_mpdu_desc_pn(pdev->htt_pdev, rx_desc, &new_pn, pn_len); /* if there was no prior PN, there's nothing to check */ if (last_pn_valid) { pn_is_replay = pdev->rx_pn[peer->security[index].sec_type].cmp( &new_pn, last_pn, index == txrx_sec_ucast, vdev->opmode); } else { last_pn_valid = peer->tids_last_pn_valid[tid] = 1; } if (pn_is_replay) { adf_nbuf_t msdu; static u_int32_t last_pncheck_print_time = 0; int log_level; u_int32_t current_time_ms; /* * This MPDU failed the PN check: * 1. Notify the control SW of the PN failure * (so countermeasures can be taken, if necessary) * 2. Discard all the MSDUs from this MPDU. */ msdu = mpdu; current_time_ms = adf_os_ticks_to_msecs(adf_os_ticks()); if (TXRX_PN_CHECK_FAILURE_PRINT_PERIOD_MS < (current_time_ms - last_pncheck_print_time)) { last_pncheck_print_time = current_time_ms; log_level = TXRX_PRINT_LEVEL_WARN; } else { log_level = TXRX_PRINT_LEVEL_INFO2; } TXRX_PRINT(log_level, "PN check failed - TID %d, peer %p " "(%02x:%02x:%02x:%02x:%02x:%02x) %s\n" " old PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" " new PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" " new seq num = %d\n", tid, peer, peer->mac_addr.raw[0], peer->mac_addr.raw[1], peer->mac_addr.raw[2], peer->mac_addr.raw[3], peer->mac_addr.raw[4], peer->mac_addr.raw[5], (index == txrx_sec_ucast) ? "ucast" : "mcast", last_pn->pn128[1], last_pn->pn128[0], last_pn->pn128[0] & 0xffffffffffffULL, new_pn.pn128[1], new_pn.pn128[0], new_pn.pn128[0] & 0xffffffffffffULL, htt_rx_mpdu_desc_seq_num(pdev->htt_pdev, rx_desc)); #if defined(ENABLE_RX_PN_TRACE) ol_rx_pn_trace_display(pdev, 1); #endif /* ENABLE_RX_PN_TRACE */ ol_rx_err( pdev->ctrl_pdev, vdev->vdev_id, peer->mac_addr.raw, tid, htt_rx_mpdu_desc_tsf32(pdev->htt_pdev, rx_desc), OL_RX_ERR_PN, mpdu, NULL, 0); /* free all MSDUs within this MPDU */ do { adf_nbuf_t next_msdu; OL_RX_ERR_STATISTICS_1(pdev, vdev, peer, rx_desc, OL_RX_ERR_PN); next_msdu = adf_nbuf_next(msdu); htt_rx_desc_frame_free(pdev->htt_pdev, msdu); if (msdu == mpdu_tail) { break; } else { msdu = next_msdu; } } while (1); } else { ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail); /* * Remember the new PN. * For simplicity, just do 2 64-bit word copies to cover the worst * case (WAPI), regardless of the length of the PN. * This is more efficient than doing a conditional branch to copy * only the relevant portion. */ last_pn->pn128[0] = new_pn.pn128[0]; last_pn->pn128[1] = new_pn.pn128[1]; OL_RX_PN_TRACE_ADD(pdev, peer, tid, rx_desc); } mpdu = next_mpdu; } /* make sure the list is null-terminated */ if (out_list_tail) { adf_nbuf_set_next(out_list_tail, NULL); } return out_list_head; }
adf_nbuf_t ol_rx_pn_check_base( struct ol_txrx_vdev_t *vdev, struct ol_txrx_peer_t *peer, unsigned tid, adf_nbuf_t msdu_list) { struct ol_txrx_pdev_t *pdev = vdev->pdev; union htt_rx_pn_t *last_pn, *global_pn, *suspect_pn; adf_nbuf_t out_list_head = NULL; adf_nbuf_t out_list_tail = NULL; adf_nbuf_t mpdu; int index; /* unicast vs. multicast */ int pn_len; void *rx_desc; int last_pn_valid; /* First, check whether the PN check applies */ rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, msdu_list); adf_os_assert(htt_rx_msdu_has_wlan_mcast_flag(pdev->htt_pdev, rx_desc)); index = htt_rx_msdu_is_wlan_mcast(pdev->htt_pdev, rx_desc) ? txrx_sec_mcast : txrx_sec_ucast; pn_len = pdev->rx_pn[peer->security[index].sec_type].len; if (pn_len == 0) { return msdu_list; } last_pn_valid = peer->tids_last_pn_valid[tid]; last_pn = &peer->tids_last_pn[tid]; global_pn = &peer->global_pn; suspect_pn = &peer->tids_suspect_pn[tid]; mpdu = msdu_list; while (mpdu) { adf_nbuf_t mpdu_tail, next_mpdu; union htt_rx_pn_t new_pn; int pn_is_replay = 0, update_last_pn = 1; #if ATH_SUPPORT_WAPI bool is_mpdu_encrypted = 0; bool is_unencrypted_pkt_wai = 0; #endif rx_desc = htt_rx_msdu_desc_retrieve(pdev->htt_pdev, mpdu); /* * Find the last MSDU within this MPDU, and * the find the first MSDU within the next MPDU. */ ol_rx_mpdu_list_next(pdev, mpdu, &mpdu_tail, &next_mpdu); #if ATH_SUPPORT_WAPI /* Don't check the PN replay for non-encrypted frames or if this is a WAI packet */ is_mpdu_encrypted = htt_rx_mpdu_is_encrypted(pdev->htt_pdev, rx_desc); is_unencrypted_pkt_wai = is_mpdu_encrypted ? false : vdev->osif_check_wai(vdev->osif_vdev, mpdu, mpdu_tail); if ((!vdev->drop_unenc && !is_mpdu_encrypted) || is_unencrypted_pkt_wai) { #else /* Don't check the PN replay for non-encrypted frames */ if (!vdev->drop_unenc && !htt_rx_mpdu_is_encrypted(pdev->htt_pdev, rx_desc)) { #endif ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail); mpdu = next_mpdu; continue; } /* retrieve PN from rx descriptor */ htt_rx_mpdu_desc_pn(pdev->htt_pdev, rx_desc, &new_pn, pn_len); /* if there was no prior PN, there's nothing to check */ if (last_pn_valid) { pn_is_replay = pdev->rx_pn[peer->security[index].sec_type].cmp( &new_pn, last_pn, index == txrx_sec_ucast, vdev->opmode); } else if (peer->authorize) { last_pn_valid = peer->tids_last_pn_valid[tid] = 1; } if (peer->authorize && peer->security[index].sec_type == htt_sec_type_aes_ccmp) { if ((new_pn.pn48 & 0xffffffffffffULL) > ((last_pn->pn48 + MAX_CCMP_PN_GAP_ERR_CHECK) & 0xffffffffffffULL)) { /* PN jump wrt last_pn is > MAX_CCMP_PN_GAP_ERR_CHECK - PN of current frame is suspected */ if (suspect_pn->pn48) { /* Check whether PN of the current frame is following prev PN seq or not */ if ((new_pn.pn48 & 0xffffffffffffULL) < (suspect_pn->pn48 & 0xffffffffffffULL)) { /* * PN number of the curr frame < PN no of prev rxed frame * As we are not sure about prev suspect PN, to detect replay, * check the current PN with global PN */ if ((new_pn.pn48 & 0xffffffffffffULL) < (global_pn->pn48 & 0xffffffffffffULL)) { /* Replay violation */ pn_is_replay = 1; } else { /* Current PN is following global PN, so mark this as suspected PN * Don't update last_pn & global_pn */ suspect_pn->pn128[0] = new_pn.pn128[0]; suspect_pn->pn128[1] = new_pn.pn128[1]; update_last_pn = 0; } } else if ((new_pn.pn48 & 0xffffffffffffULL) < ((suspect_pn->pn48 + MAX_CCMP_PN_GAP_ERR_CHECK) & 0xffffffffffffULL)) { /* Current PN is following prev suspected PN seq * Update last_pn & global_pn (update_last_pn = 1;) */ } else { /* * Current PN is neither following prev suspected PN nor last_pn * Mark this as new suspect and don't update last_pn & global_pn */ suspect_pn->pn128[0] = new_pn.pn128[0]; suspect_pn->pn128[1] = new_pn.pn128[1]; update_last_pn = 0; } } else { /* New Jump in PN observed * So mark this PN as suspected and don't update last_pn/global_pn */ suspect_pn->pn128[0] = new_pn.pn128[0]; suspect_pn->pn128[1] = new_pn.pn128[1]; update_last_pn = 0; } } else { /* Valid PN, update last_pn & global_pn (update_last_pn = 1;) */ } } if (pn_is_replay) { adf_nbuf_t msdu; /* * This MPDU failed the PN check: * 1. Notify the control SW of the PN failure * (so countermeasures can be taken, if necessary) * 2. Discard all the MSDUs from this MPDU. */ msdu = mpdu; TXRX_PRINT(TXRX_PRINT_LEVEL_INFO1, "PN check failed on offload path- TID %d, peer %p " "(%02x:%02x:%02x:%02x:%02x:%02x) %s\n" " old PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" " new PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" " global PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" " suspect PN (u64 x2)= 0x%08llx %08llx (LSBs = %lld)\n" #if RX_DEBUG " htt_status = %d\n" #endif " prev seq num = %d\n", " new seq num = %d\n", tid, peer, peer->mac_addr.raw[0], peer->mac_addr.raw[1], peer->mac_addr.raw[2], peer->mac_addr.raw[3], peer->mac_addr.raw[4], peer->mac_addr.raw[5], (index == txrx_sec_ucast) ? "ucast" : "mcast", last_pn->pn128[1], last_pn->pn128[0], last_pn->pn128[0] & 0xffffffffffffULL, new_pn.pn128[1], new_pn.pn128[0], new_pn.pn128[0] & 0xffffffffffffULL, global_pn->pn128[1], global_pn->pn128[0], global_pn->pn128[0] & 0xffffffffffffULL, suspect_pn->pn128[1], suspect_pn->pn128[0], suspect_pn->pn128[0] & 0xffffffffffffULL, #if RX_DEBUG htt_rx_mpdu_status(pdev->htt_pdev), #endif peer->tids_last_seq[tid], htt_rx_mpdu_desc_seq_num(pdev->htt_pdev, rx_desc)); ol_rx_pn_trace_display(pdev, 1); ol_rx_err( pdev->ctrl_pdev, vdev->vdev_id, peer->mac_addr.raw, tid, htt_rx_mpdu_desc_tsf32(pdev->htt_pdev, rx_desc), OL_RX_ERR_PN, mpdu); /* free all MSDUs within this MPDU */ do { adf_nbuf_t next_msdu; next_msdu = adf_nbuf_next(msdu); htt_rx_desc_frame_free(pdev->htt_pdev, msdu); if (msdu == mpdu_tail) { break; } else { msdu = next_msdu; } } while (1); } else { ADD_MPDU_TO_LIST(out_list_head, out_list_tail, mpdu, mpdu_tail); if(peer->authorize) { /* * Remember the new PN. * For simplicity, just do 2 64-bit word copies to cover the worst * case (WAPI), regardless of the length of the PN. * This is more efficient than doing a conditional branch to copy * only the relevant portion. */ if (update_last_pn) { last_pn->pn128[0] = new_pn.pn128[0]; last_pn->pn128[1] = new_pn.pn128[1]; global_pn->pn128[0] = new_pn.pn128[0]; global_pn->pn128[1] = new_pn.pn128[1]; suspect_pn->pn128[0] = 0; suspect_pn->pn128[1] = 0; } OL_RX_PN_TRACE_ADD(pdev, peer, tid, rx_desc); } } mpdu = next_mpdu; } /* make sure the list is null-terminated */ if (out_list_tail) { adf_nbuf_set_next(out_list_tail, NULL); } return out_list_head; } void ol_rx_pn_check( struct ol_txrx_vdev_t *vdev, struct ol_txrx_peer_t *peer, unsigned tid, adf_nbuf_t msdu_list) { msdu_list = ol_rx_pn_check_base(vdev, peer, tid, msdu_list); ol_rx_fwd_check(vdev, peer, tid, msdu_list); }