]> git.karo-electronics.de Git - karo-tx-linux.git/blobdiff - net/mac80211/mesh_plink.c
Merge branch 'for-john' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211
[karo-tx-linux.git] / net / mac80211 / mesh_plink.c
index 4301aa5aa227c3d539cd0bf89bdf5b004679976a..e8f60aa2e848b16982993f134fc63511186542af 100644 (file)
 #define mod_plink_timer(s, t) (mod_timer(&s->plink_timer, \
                                jiffies + HZ * t / 1000))
 
-/* We only need a valid sta if user configured a minimum rssi_threshold. */
-#define rssi_threshold_check(sta, sdata) \
-               (sdata->u.mesh.mshcfg.rssi_threshold == 0 ||\
-               (sta && (s8) -ewma_read(&sta->avg_signal) > \
-               sdata->u.mesh.mshcfg.rssi_threshold))
-
 enum plink_event {
        PLINK_UNDEFINED,
        OPN_ACPT,
@@ -61,7 +55,17 @@ static const char * const mplevents[] = {
 
 static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
                               enum ieee80211_self_protected_actioncode action,
-                              u8 *da, __le16 llid, __le16 plid, __le16 reason);
+                              u8 *da, u16 llid, u16 plid, u16 reason);
+
+
+/* We only need a valid sta if user configured a minimum rssi_threshold. */
+static bool rssi_threshold_check(struct ieee80211_sub_if_data *sdata,
+                                struct sta_info *sta)
+{
+       s32 rssi_threshold = sdata->u.mesh.mshcfg.rssi_threshold;
+       return rssi_threshold == 0 ||
+              (sta && (s8) -ewma_read(&sta->avg_signal) > rssi_threshold);
+}
 
 /**
  * mesh_plink_fsm_restart - restart a mesh peer link finite state machine
@@ -242,7 +246,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta)
 
        spin_lock_bh(&sta->lock);
        changed = __mesh_plink_deactivate(sta);
-       sta->reason = cpu_to_le16(WLAN_REASON_MESH_PEER_CANCELED);
+       sta->reason = WLAN_REASON_MESH_PEER_CANCELED;
        mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
                            sta->sta.addr, sta->llid, sta->plid,
                            sta->reason);
@@ -253,7 +257,7 @@ u32 mesh_plink_deactivate(struct sta_info *sta)
 
 static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
                               enum ieee80211_self_protected_actioncode action,
-                              u8 *da, __le16 llid, __le16 plid, __le16 reason)
+                              u8 *da, u16 llid, u16 plid, u16 reason)
 {
        struct ieee80211_local *local = sdata->local;
        struct sk_buff *skb;
@@ -279,7 +283,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
                            2 + 8 + /* peering IE */
                            sdata->u.mesh.ie_len);
        if (!skb)
-               return -1;
+               return err;
        info = IEEE80211_SKB_CB(skb);
        skb_reserve(skb, local->tx_headroom);
        mgmt = (struct ieee80211_mgmt *) skb_put(skb, hdr_len);
@@ -301,7 +305,7 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
                if (action == WLAN_SP_MESH_PEERING_CONFIRM) {
                        /* AID */
                        pos = skb_put(skb, 2);
-                       memcpy(pos + 2, &plid, 2);
+                       put_unaligned_le16(plid, pos + 2);
                }
                if (ieee80211_add_srates_ie(sdata, skb, true, band) ||
                    ieee80211_add_ext_srates_ie(sdata, skb, true, band) ||
@@ -343,14 +347,14 @@ static int mesh_plink_frame_tx(struct ieee80211_sub_if_data *sdata,
        *pos++ = ie_len;
        memcpy(pos, &peering_proto, 2);
        pos += 2;
-       memcpy(pos, &llid, 2);
+       put_unaligned_le16(llid, pos);
        pos += 2;
        if (include_plid) {
-               memcpy(pos, &plid, 2);
+               put_unaligned_le16(plid, pos);
                pos += 2;
        }
        if (action == WLAN_SP_MESH_PEERING_CLOSE) {
-               memcpy(pos, &reason, 2);
+               put_unaligned_le16(reason, pos);
                pos += 2;
        }
 
@@ -433,6 +437,7 @@ __mesh_sta_info_alloc(struct ieee80211_sub_if_data *sdata, u8 *hw_addr)
        sta_info_pre_move_state(sta, IEEE80211_STA_AUTHORIZED);
 
        set_sta_flag(sta, WLAN_STA_WME);
+       sta->sta.wme = true;
 
        return sta;
 }
@@ -518,7 +523,7 @@ void mesh_neighbour_update(struct ieee80211_sub_if_data *sdata,
            sta->plink_state == NL80211_PLINK_LISTEN &&
            sdata->u.mesh.accepting_plinks &&
            sdata->u.mesh.mshcfg.auto_open_plinks &&
-           rssi_threshold_check(sta, sdata))
+           rssi_threshold_check(sdata, sta))
                changed = mesh_plink_open(sta);
 
        ieee80211_mps_frame_release(sta, elems);
@@ -530,9 +535,10 @@ out:
 static void mesh_plink_timer(unsigned long data)
 {
        struct sta_info *sta;
-       __le16 llid, plid, reason;
+       u16 reason = 0;
        struct ieee80211_sub_if_data *sdata;
        struct mesh_config *mshcfg;
+       enum ieee80211_self_protected_actioncode action = 0;
 
        /*
         * This STA is valid because sta_info_destroy() will
@@ -553,9 +559,6 @@ static void mesh_plink_timer(unsigned long data)
        mpl_dbg(sta->sdata,
                "Mesh plink timer for %pM fired on state %s\n",
                sta->sta.addr, mplstates[sta->plink_state]);
-       reason = 0;
-       llid = sta->llid;
-       plid = sta->plid;
        sdata = sta->sdata;
        mshcfg = &sdata->u.mesh.mshcfg;
 
@@ -574,33 +577,31 @@ static void mesh_plink_timer(unsigned long data)
                                             rand % sta->plink_timeout;
                        ++sta->plink_retries;
                        mod_plink_timer(sta, sta->plink_timeout);
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
-                                           sta->sta.addr, llid, 0, 0);
+                       action = WLAN_SP_MESH_PEERING_OPEN;
                        break;
                }
-               reason = cpu_to_le16(WLAN_REASON_MESH_MAX_RETRIES);
+               reason = WLAN_REASON_MESH_MAX_RETRIES;
                /* fall through on else */
        case NL80211_PLINK_CNF_RCVD:
                /* confirm timer */
                if (!reason)
-                       reason = cpu_to_le16(WLAN_REASON_MESH_CONFIRM_TIMEOUT);
+                       reason = WLAN_REASON_MESH_CONFIRM_TIMEOUT;
                sta->plink_state = NL80211_PLINK_HOLDING;
                mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
-               spin_unlock_bh(&sta->lock);
-               mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
-                                   sta->sta.addr, llid, plid, reason);
+               action = WLAN_SP_MESH_PEERING_CLOSE;
                break;
        case NL80211_PLINK_HOLDING:
                /* holding timer */
                del_timer(&sta->plink_timer);
                mesh_plink_fsm_restart(sta);
-               spin_unlock_bh(&sta->lock);
                break;
        default:
-               spin_unlock_bh(&sta->lock);
                break;
        }
+       spin_unlock_bh(&sta->lock);
+       if (action)
+               mesh_plink_frame_tx(sdata, action, sta->sta.addr,
+                                   sta->llid, sta->plid, reason);
 }
 
 static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
@@ -612,9 +613,40 @@ static inline void mesh_plink_timer_set(struct sta_info *sta, int timeout)
        add_timer(&sta->plink_timer);
 }
 
+static bool llid_in_use(struct ieee80211_sub_if_data *sdata,
+                       u16 llid)
+{
+       struct ieee80211_local *local = sdata->local;
+       bool in_use = false;
+       struct sta_info *sta;
+
+       rcu_read_lock();
+       list_for_each_entry_rcu(sta, &local->sta_list, list) {
+               if (!memcmp(&sta->llid, &llid, sizeof(llid))) {
+                       in_use = true;
+                       break;
+               }
+       }
+       rcu_read_unlock();
+
+       return in_use;
+}
+
+static u16 mesh_get_new_llid(struct ieee80211_sub_if_data *sdata)
+{
+       u16 llid;
+
+       do {
+               get_random_bytes(&llid, sizeof(llid));
+               /* for mesh PS we still only have the AID range for TIM bits */
+               llid = (llid % IEEE80211_MAX_AID) + 1;
+       } while (llid_in_use(sdata, llid));
+
+       return llid;
+}
+
 u32 mesh_plink_open(struct sta_info *sta)
 {
-       __le16 llid;
        struct ieee80211_sub_if_data *sdata = sta->sdata;
        u32 changed;
 
@@ -622,8 +654,7 @@ u32 mesh_plink_open(struct sta_info *sta)
                return 0;
 
        spin_lock_bh(&sta->lock);
-       get_random_bytes(&llid, 2);
-       sta->llid = llid;
+       sta->llid = mesh_get_new_llid(sdata);
        if (sta->plink_state != NL80211_PLINK_LISTEN &&
            sta->plink_state != NL80211_PLINK_BLOCKED) {
                spin_unlock_bh(&sta->lock);
@@ -640,7 +671,7 @@ u32 mesh_plink_open(struct sta_info *sta)
        changed = ieee80211_mps_local_status_update(sdata);
 
        mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_OPEN,
-                           sta->sta.addr, llid, 0, 0);
+                           sta->sta.addr, sta->llid, 0, 0);
        return changed;
 }
 
@@ -656,390 +687,147 @@ u32 mesh_plink_block(struct sta_info *sta)
        return changed;
 }
 
-
-void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
-                        struct ieee80211_mgmt *mgmt, size_t len,
-                        struct ieee80211_rx_status *rx_status)
+static void mesh_plink_close(struct ieee80211_sub_if_data *sdata,
+                            struct sta_info *sta,
+                            enum plink_event event)
 {
        struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
-       struct ieee802_11_elems elems;
-       struct sta_info *sta;
-       enum plink_event event;
-       enum ieee80211_self_protected_actioncode ftype;
-       size_t baselen;
-       bool matches_local = true;
-       u8 ie_len;
-       u8 *baseaddr;
-       u32 changed = 0;
-       __le16 plid, llid, reason;
-
-       /* need action_code, aux */
-       if (len < IEEE80211_MIN_ACTION_SIZE + 3)
-               return;
-
-       if (sdata->u.mesh.user_mpm)
-               /* userspace must register for these */
-               return;
-
-       if (is_multicast_ether_addr(mgmt->da)) {
-               mpl_dbg(sdata,
-                       "Mesh plink: ignore frame from multicast address\n");
-               return;
-       }
-
-       baseaddr = mgmt->u.action.u.self_prot.variable;
-       baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
-       if (mgmt->u.action.u.self_prot.action_code ==
-                                               WLAN_SP_MESH_PEERING_CONFIRM) {
-               baseaddr += 4;
-               baselen += 4;
-       }
-       ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems);
-
-       if (!elems.peering) {
-               mpl_dbg(sdata,
-                       "Mesh plink: missing necessary peer link ie\n");
-               return;
-       }
 
-       if (elems.rsn_len &&
-           sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
-               mpl_dbg(sdata,
-                       "Mesh plink: can't establish link with secure peer\n");
-               return;
-       }
+       u16 reason = (event == CLS_ACPT) ?
+                    WLAN_REASON_MESH_CLOSE : WLAN_REASON_MESH_CONFIG;
 
-       ftype = mgmt->u.action.u.self_prot.action_code;
-       ie_len = elems.peering_len;
-       if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
-           (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
-           (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
-                                                       && ie_len != 8)) {
-               mpl_dbg(sdata,
-                       "Mesh plink: incorrect plink ie length %d %d\n",
-                       ftype, ie_len);
-               return;
-       }
-
-       if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
-           (!elems.mesh_id || !elems.mesh_config)) {
-               mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
-               return;
-       }
-       /* Note the lines below are correct, the llid in the frame is the plid
-        * from the point of view of this host.
-        */
-       memcpy(&plid, PLINK_GET_LLID(elems.peering), 2);
-       if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
-           (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8))
-               memcpy(&llid, PLINK_GET_PLID(elems.peering), 2);
-
-       /* WARNING: Only for sta pointer, is dropped & re-acquired */
-       rcu_read_lock();
-
-       sta = sta_info_get(sdata, mgmt->sa);
-       if (!sta && ftype != WLAN_SP_MESH_PEERING_OPEN) {
-               mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
-               rcu_read_unlock();
-               return;
-       }
-
-       if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
-           !rssi_threshold_check(sta, sdata)) {
-               mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
-                       mgmt->sa);
-               rcu_read_unlock();
-               return;
-       }
-
-       if (sta && !test_sta_flag(sta, WLAN_STA_AUTH)) {
-               mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
-               rcu_read_unlock();
-               return;
-       }
+       sta->reason = reason;
+       sta->plink_state = NL80211_PLINK_HOLDING;
+       mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
+}
 
-       if (sta && sta->plink_state == NL80211_PLINK_BLOCKED) {
-               rcu_read_unlock();
-               return;
-       }
+static u32 mesh_plink_establish(struct ieee80211_sub_if_data *sdata,
+                               struct sta_info *sta)
+{
+       struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
+       u32 changed = 0;
 
-       /* Now we will figure out the appropriate event... */
-       event = PLINK_UNDEFINED;
-       if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
-           !mesh_matches_local(sdata, &elems)) {
-               matches_local = false;
-               switch (ftype) {
-               case WLAN_SP_MESH_PEERING_OPEN:
-                       event = OPN_RJCT;
-                       break;
-               case WLAN_SP_MESH_PEERING_CONFIRM:
-                       event = CNF_RJCT;
-                       break;
-               default:
-                       break;
-               }
-       }
+       del_timer(&sta->plink_timer);
+       sta->plink_state = NL80211_PLINK_ESTAB;
+       changed |= mesh_plink_inc_estab_count(sdata);
+       changed |= mesh_set_ht_prot_mode(sdata);
+       changed |= mesh_set_short_slot_time(sdata);
+       mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n", sta->sta.addr);
+       ieee80211_mps_sta_status_update(sta);
+       changed |= ieee80211_mps_set_sta_local_pm(sta, mshcfg->power_mode);
+       return changed;
+}
 
-       if (!sta && !matches_local) {
-               rcu_read_unlock();
-               reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
-               llid = 0;
-               mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
-                                   mgmt->sa, llid, plid, reason);
-               return;
-       } else if (!sta) {
-               /* ftype == WLAN_SP_MESH_PEERING_OPEN */
-               if (!mesh_plink_free_count(sdata)) {
-                       mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
-                       rcu_read_unlock();
-                       return;
-               }
-               event = OPN_ACPT;
-       } else if (matches_local) {
-               switch (ftype) {
-               case WLAN_SP_MESH_PEERING_OPEN:
-                       if (!mesh_plink_free_count(sdata) ||
-                           (sta->plid && sta->plid != plid))
-                               event = OPN_IGNR;
-                       else
-                               event = OPN_ACPT;
-                       break;
-               case WLAN_SP_MESH_PEERING_CONFIRM:
-                       if (!mesh_plink_free_count(sdata) ||
-                           (sta->llid != llid || sta->plid != plid))
-                               event = CNF_IGNR;
-                       else
-                               event = CNF_ACPT;
-                       break;
-               case WLAN_SP_MESH_PEERING_CLOSE:
-                       if (sta->plink_state == NL80211_PLINK_ESTAB)
-                               /* Do not check for llid or plid. This does not
-                                * follow the standard but since multiple plinks
-                                * per sta are not supported, it is necessary in
-                                * order to avoid a livelock when MP A sees an
-                                * establish peer link to MP B but MP B does not
-                                * see it. This can be caused by a timeout in
-                                * B's peer link establishment or B beign
-                                * restarted.
-                                */
-                               event = CLS_ACPT;
-                       else if (sta->plid != plid)
-                               event = CLS_IGNR;
-                       else if (ie_len == 7 && sta->llid != llid)
-                               event = CLS_IGNR;
-                       else
-                               event = CLS_ACPT;
-                       break;
-               default:
-                       mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
-                       rcu_read_unlock();
-                       return;
-               }
-       }
+/**
+ * mesh_plink_fsm - step @sta MPM based on @event
+ *
+ * @sdata: interface
+ * @sta: mesh neighbor
+ * @event: peering event
+ *
+ * Return: changed MBSS flags
+ */
+static u32 mesh_plink_fsm(struct ieee80211_sub_if_data *sdata,
+                         struct sta_info *sta, enum plink_event event)
+{
+       struct mesh_config *mshcfg = &sdata->u.mesh.mshcfg;
+       enum ieee80211_self_protected_actioncode action = 0;
+       u32 changed = 0;
 
-       if (event == OPN_ACPT) {
-               rcu_read_unlock();
-               /* allocate sta entry if necessary and update info */
-               sta = mesh_sta_info_get(sdata, mgmt->sa, &elems);
-               if (!sta) {
-                       mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
-                       rcu_read_unlock();
-                       return;
-               }
-       }
+       mpl_dbg(sdata, "peer %pM in state %s got event %s\n", sta->sta.addr,
+               mplstates[sta->plink_state], mplevents[event]);
 
-       mpl_dbg(sdata, "peer %pM in state %s got event %s\n", mgmt->sa,
-                      mplstates[sta->plink_state], mplevents[event]);
-       reason = 0;
        spin_lock_bh(&sta->lock);
        switch (sta->plink_state) {
-               /* spin_unlock as soon as state is updated at each case */
        case NL80211_PLINK_LISTEN:
                switch (event) {
                case CLS_ACPT:
                        mesh_plink_fsm_restart(sta);
-                       spin_unlock_bh(&sta->lock);
                        break;
                case OPN_ACPT:
                        sta->plink_state = NL80211_PLINK_OPN_RCVD;
-                       sta->plid = plid;
-                       get_random_bytes(&llid, 2);
-                       sta->llid = llid;
+                       sta->llid = mesh_get_new_llid(sdata);
                        mesh_plink_timer_set(sta,
                                             mshcfg->dot11MeshRetryTimeout);
 
                        /* set the non-peer mode to active during peering */
                        changed |= ieee80211_mps_local_status_update(sdata);
-
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_OPEN,
-                                           sta->sta.addr, llid, 0, 0);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CONFIRM,
-                                           sta->sta.addr, llid, plid, 0);
+                       action = WLAN_SP_MESH_PEERING_OPEN;
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
                        break;
                }
                break;
-
        case NL80211_PLINK_OPN_SNT:
                switch (event) {
                case OPN_RJCT:
                case CNF_RJCT:
-                       reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
                case CLS_ACPT:
-                       if (!reason)
-                               reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
-                       sta->reason = reason;
-                       sta->plink_state = NL80211_PLINK_HOLDING;
-                       if (!mod_plink_timer(sta,
-                                            mshcfg->dot11MeshHoldingTimeout))
-                               sta->ignore_plink_timer = true;
-
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CLOSE,
-                                           sta->sta.addr, llid, plid, reason);
+                       mesh_plink_close(sdata, sta, event);
+                       action = WLAN_SP_MESH_PEERING_CLOSE;
                        break;
                case OPN_ACPT:
                        /* retry timer is left untouched */
                        sta->plink_state = NL80211_PLINK_OPN_RCVD;
-                       sta->plid = plid;
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CONFIRM,
-                                           sta->sta.addr, llid, plid, 0);
+                       action = WLAN_SP_MESH_PEERING_CONFIRM;
                        break;
                case CNF_ACPT:
                        sta->plink_state = NL80211_PLINK_CNF_RCVD;
                        if (!mod_plink_timer(sta,
                                             mshcfg->dot11MeshConfirmTimeout))
                                sta->ignore_plink_timer = true;
-
-                       spin_unlock_bh(&sta->lock);
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
                        break;
                }
                break;
-
        case NL80211_PLINK_OPN_RCVD:
                switch (event) {
                case OPN_RJCT:
                case CNF_RJCT:
-                       reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
                case CLS_ACPT:
-                       if (!reason)
-                               reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
-                       sta->reason = reason;
-                       sta->plink_state = NL80211_PLINK_HOLDING;
-                       if (!mod_plink_timer(sta,
-                                            mshcfg->dot11MeshHoldingTimeout))
-                               sta->ignore_plink_timer = true;
-
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
-                                           sta->sta.addr, llid, plid, reason);
+                       mesh_plink_close(sdata, sta, event);
+                       action = WLAN_SP_MESH_PEERING_CLOSE;
                        break;
                case OPN_ACPT:
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CONFIRM,
-                                           sta->sta.addr, llid, plid, 0);
+                       action = WLAN_SP_MESH_PEERING_CONFIRM;
                        break;
                case CNF_ACPT:
-                       del_timer(&sta->plink_timer);
-                       sta->plink_state = NL80211_PLINK_ESTAB;
-                       spin_unlock_bh(&sta->lock);
-                       changed |= mesh_plink_inc_estab_count(sdata);
-                       changed |= mesh_set_ht_prot_mode(sdata);
-                       changed |= mesh_set_short_slot_time(sdata);
-                       mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n",
-                               sta->sta.addr);
-                       ieee80211_mps_sta_status_update(sta);
-                       changed |= ieee80211_mps_set_sta_local_pm(sta,
-                                                      mshcfg->power_mode);
+                       changed |= mesh_plink_establish(sdata, sta);
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
                        break;
                }
                break;
-
        case NL80211_PLINK_CNF_RCVD:
                switch (event) {
                case OPN_RJCT:
                case CNF_RJCT:
-                       reason = cpu_to_le16(WLAN_REASON_MESH_CONFIG);
                case CLS_ACPT:
-                       if (!reason)
-                               reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
-                       sta->reason = reason;
-                       sta->plink_state = NL80211_PLINK_HOLDING;
-                       if (!mod_plink_timer(sta,
-                                            mshcfg->dot11MeshHoldingTimeout))
-                               sta->ignore_plink_timer = true;
-
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CLOSE,
-                                           sta->sta.addr, llid, plid, reason);
+                       mesh_plink_close(sdata, sta, event);
+                       action = WLAN_SP_MESH_PEERING_CLOSE;
                        break;
                case OPN_ACPT:
-                       del_timer(&sta->plink_timer);
-                       sta->plink_state = NL80211_PLINK_ESTAB;
-                       spin_unlock_bh(&sta->lock);
-                       changed |= mesh_plink_inc_estab_count(sdata);
-                       changed |= mesh_set_ht_prot_mode(sdata);
-                       changed |= mesh_set_short_slot_time(sdata);
-                       mpl_dbg(sdata, "Mesh plink with %pM ESTABLISHED\n",
-                               sta->sta.addr);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CONFIRM,
-                                           sta->sta.addr, llid, plid, 0);
-                       ieee80211_mps_sta_status_update(sta);
-                       changed |= ieee80211_mps_set_sta_local_pm(sta,
-                                                       mshcfg->power_mode);
+                       changed |= mesh_plink_establish(sdata, sta);
+                       action = WLAN_SP_MESH_PEERING_CONFIRM;
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
                        break;
                }
                break;
-
        case NL80211_PLINK_ESTAB:
                switch (event) {
                case CLS_ACPT:
-                       reason = cpu_to_le16(WLAN_REASON_MESH_CLOSE);
-                       sta->reason = reason;
                        changed |= __mesh_plink_deactivate(sta);
-                       sta->plink_state = NL80211_PLINK_HOLDING;
-                       llid = sta->llid;
-                       mod_plink_timer(sta, mshcfg->dot11MeshHoldingTimeout);
-                       spin_unlock_bh(&sta->lock);
                        changed |= mesh_set_ht_prot_mode(sdata);
                        changed |= mesh_set_short_slot_time(sdata);
-                       mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
-                                           sta->sta.addr, llid, plid, reason);
+                       mesh_plink_close(sdata, sta, event);
+                       action = WLAN_SP_MESH_PEERING_CLOSE;
                        break;
                case OPN_ACPT:
-                       llid = sta->llid;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata,
-                                           WLAN_SP_MESH_PEERING_CONFIRM,
-                                           sta->sta.addr, llid, plid, 0);
+                       action = WLAN_SP_MESH_PEERING_CONFIRM;
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
                        break;
                }
                break;
@@ -1049,32 +837,271 @@ void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
                        if (del_timer(&sta->plink_timer))
                                sta->ignore_plink_timer = 1;
                        mesh_plink_fsm_restart(sta);
-                       spin_unlock_bh(&sta->lock);
                        break;
                case OPN_ACPT:
                case CNF_ACPT:
                case OPN_RJCT:
                case CNF_RJCT:
-                       llid = sta->llid;
-                       reason = sta->reason;
-                       spin_unlock_bh(&sta->lock);
-                       mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
-                                           sta->sta.addr, llid, plid, reason);
+                       action = WLAN_SP_MESH_PEERING_CLOSE;
                        break;
                default:
-                       spin_unlock_bh(&sta->lock);
+                       break;
                }
                break;
        default:
                /* should not get here, PLINK_BLOCKED is dealt with at the
                 * beginning of the function
                 */
-               spin_unlock_bh(&sta->lock);
                break;
        }
+       spin_unlock_bh(&sta->lock);
+       if (action) {
+               mesh_plink_frame_tx(sdata, action, sta->sta.addr,
+                                   sta->llid, sta->plid, sta->reason);
+
+               /* also send confirm in open case */
+               if (action == WLAN_SP_MESH_PEERING_OPEN) {
+                       mesh_plink_frame_tx(sdata,
+                                           WLAN_SP_MESH_PEERING_CONFIRM,
+                                           sta->sta.addr, sta->llid,
+                                           sta->plid, 0);
+               }
+       }
+
+       return changed;
+}
+
+/*
+ * mesh_plink_get_event - get correct MPM event
+ *
+ * @sdata: interface
+ * @sta: peer, leave NULL if processing a frame from a new suitable peer
+ * @elems: peering management IEs
+ * @ftype: frame type
+ * @llid: peer's peer link ID
+ * @plid: peer's local link ID
+ *
+ * Return: new peering event for @sta, but PLINK_UNDEFINED should be treated as
+ * an error.
+ */
+static enum plink_event
+mesh_plink_get_event(struct ieee80211_sub_if_data *sdata,
+                    struct sta_info *sta,
+                    struct ieee802_11_elems *elems,
+                    enum ieee80211_self_protected_actioncode ftype,
+                    u16 llid, u16 plid)
+{
+       enum plink_event event = PLINK_UNDEFINED;
+       u8 ie_len = elems->peering_len;
+       bool matches_local;
+
+       matches_local = (ftype == WLAN_SP_MESH_PEERING_CLOSE ||
+                        mesh_matches_local(sdata, elems));
+
+       /* deny open request from non-matching peer */
+       if (!matches_local && !sta) {
+               event = OPN_RJCT;
+               goto out;
+       }
+
+       if (!sta) {
+               if (ftype != WLAN_SP_MESH_PEERING_OPEN) {
+                       mpl_dbg(sdata, "Mesh plink: cls or cnf from unknown peer\n");
+                       goto out;
+               }
+               /* ftype == WLAN_SP_MESH_PEERING_OPEN */
+               if (!mesh_plink_free_count(sdata)) {
+                       mpl_dbg(sdata, "Mesh plink error: no more free plinks\n");
+                       goto out;
+               }
+       } else {
+               if (!test_sta_flag(sta, WLAN_STA_AUTH)) {
+                       mpl_dbg(sdata, "Mesh plink: Action frame from non-authed peer\n");
+                       goto out;
+               }
+               if (sta->plink_state == NL80211_PLINK_BLOCKED)
+                       goto out;
+       }
+
+       /* new matching peer */
+       if (!sta) {
+               event = OPN_ACPT;
+               goto out;
+       }
+
+       switch (ftype) {
+       case WLAN_SP_MESH_PEERING_OPEN:
+               if (!matches_local)
+                       event = OPN_RJCT;
+               if (!mesh_plink_free_count(sdata) ||
+                   (sta->plid && sta->plid != plid))
+                       event = OPN_IGNR;
+               else
+                       event = OPN_ACPT;
+               break;
+       case WLAN_SP_MESH_PEERING_CONFIRM:
+               if (!matches_local)
+                       event = CNF_RJCT;
+               if (!mesh_plink_free_count(sdata) ||
+                   (sta->llid != llid || sta->plid != plid))
+                       event = CNF_IGNR;
+               else
+                       event = CNF_ACPT;
+               break;
+       case WLAN_SP_MESH_PEERING_CLOSE:
+               if (sta->plink_state == NL80211_PLINK_ESTAB)
+                       /* Do not check for llid or plid. This does not
+                        * follow the standard but since multiple plinks
+                        * per sta are not supported, it is necessary in
+                        * order to avoid a livelock when MP A sees an
+                        * establish peer link to MP B but MP B does not
+                        * see it. This can be caused by a timeout in
+                        * B's peer link establishment or B beign
+                        * restarted.
+                        */
+                       event = CLS_ACPT;
+               else if (sta->plid != plid)
+                       event = CLS_IGNR;
+               else if (ie_len == 8 && sta->llid != llid)
+                       event = CLS_IGNR;
+               else
+                       event = CLS_ACPT;
+               break;
+       default:
+               mpl_dbg(sdata, "Mesh plink: unknown frame subtype\n");
+               break;
+       }
+
+out:
+       return event;
+}
 
+static void
+mesh_process_plink_frame(struct ieee80211_sub_if_data *sdata,
+                        struct ieee80211_mgmt *mgmt,
+                        struct ieee802_11_elems *elems)
+{
+
+       struct sta_info *sta;
+       enum plink_event event;
+       enum ieee80211_self_protected_actioncode ftype;
+       u32 changed = 0;
+       u8 ie_len = elems->peering_len;
+       __le16 _plid, _llid;
+       u16 plid, llid = 0;
+
+       if (!elems->peering) {
+               mpl_dbg(sdata,
+                       "Mesh plink: missing necessary peer link ie\n");
+               return;
+       }
+
+       if (elems->rsn_len &&
+           sdata->u.mesh.security == IEEE80211_MESH_SEC_NONE) {
+               mpl_dbg(sdata,
+                       "Mesh plink: can't establish link with secure peer\n");
+               return;
+       }
+
+       ftype = mgmt->u.action.u.self_prot.action_code;
+       if ((ftype == WLAN_SP_MESH_PEERING_OPEN && ie_len != 4) ||
+           (ftype == WLAN_SP_MESH_PEERING_CONFIRM && ie_len != 6) ||
+           (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len != 6
+                                                       && ie_len != 8)) {
+               mpl_dbg(sdata,
+                       "Mesh plink: incorrect plink ie length %d %d\n",
+                       ftype, ie_len);
+               return;
+       }
+
+       if (ftype != WLAN_SP_MESH_PEERING_CLOSE &&
+           (!elems->mesh_id || !elems->mesh_config)) {
+               mpl_dbg(sdata, "Mesh plink: missing necessary ie\n");
+               return;
+       }
+       /* Note the lines below are correct, the llid in the frame is the plid
+        * from the point of view of this host.
+        */
+       memcpy(&_plid, PLINK_GET_LLID(elems->peering), sizeof(__le16));
+       plid = le16_to_cpu(_plid);
+       if (ftype == WLAN_SP_MESH_PEERING_CONFIRM ||
+           (ftype == WLAN_SP_MESH_PEERING_CLOSE && ie_len == 8)) {
+               memcpy(&_llid, PLINK_GET_PLID(elems->peering), sizeof(__le16));
+               llid = le16_to_cpu(_llid);
+       }
+
+       /* WARNING: Only for sta pointer, is dropped & re-acquired */
+       rcu_read_lock();
+
+       sta = sta_info_get(sdata, mgmt->sa);
+
+       if (ftype == WLAN_SP_MESH_PEERING_OPEN &&
+           !rssi_threshold_check(sdata, sta)) {
+               mpl_dbg(sdata, "Mesh plink: %pM does not meet rssi threshold\n",
+                       mgmt->sa);
+               goto unlock_rcu;
+       }
+
+       /* Now we will figure out the appropriate event... */
+       event = mesh_plink_get_event(sdata, sta, elems, ftype, llid, plid);
+
+       if (event == OPN_ACPT) {
+               rcu_read_unlock();
+               /* allocate sta entry if necessary and update info */
+               sta = mesh_sta_info_get(sdata, mgmt->sa, elems);
+               if (!sta) {
+                       mpl_dbg(sdata, "Mesh plink: failed to init peer!\n");
+                       goto unlock_rcu;
+               }
+               sta->plid = plid;
+       } else if (!sta && event == OPN_RJCT) {
+               mesh_plink_frame_tx(sdata, WLAN_SP_MESH_PEERING_CLOSE,
+                                   mgmt->sa, 0, plid,
+                                   WLAN_REASON_MESH_CONFIG);
+               goto unlock_rcu;
+       } else if (!sta || event == PLINK_UNDEFINED) {
+               /* something went wrong */
+               goto unlock_rcu;
+       }
+
+       changed |= mesh_plink_fsm(sdata, sta, event);
+
+unlock_rcu:
        rcu_read_unlock();
 
        if (changed)
                ieee80211_mbss_info_change_notify(sdata, changed);
 }
+
+void mesh_rx_plink_frame(struct ieee80211_sub_if_data *sdata,
+                        struct ieee80211_mgmt *mgmt, size_t len,
+                        struct ieee80211_rx_status *rx_status)
+{
+       struct ieee802_11_elems elems;
+       size_t baselen;
+       u8 *baseaddr;
+
+       /* need action_code, aux */
+       if (len < IEEE80211_MIN_ACTION_SIZE + 3)
+               return;
+
+       if (sdata->u.mesh.user_mpm)
+               /* userspace must register for these */
+               return;
+
+       if (is_multicast_ether_addr(mgmt->da)) {
+               mpl_dbg(sdata,
+                       "Mesh plink: ignore frame from multicast address\n");
+               return;
+       }
+
+       baseaddr = mgmt->u.action.u.self_prot.variable;
+       baselen = (u8 *) mgmt->u.action.u.self_prot.variable - (u8 *) mgmt;
+       if (mgmt->u.action.u.self_prot.action_code ==
+                                               WLAN_SP_MESH_PEERING_CONFIRM) {
+               baseaddr += 4;
+               baselen += 4;
+       }
+       ieee802_11_parse_elems(baseaddr, len - baselen, true, &elems);
+       mesh_process_plink_frame(sdata, mgmt, &elems);
+}