Commit d971650e authored by David S. Miller's avatar David S. Miller

Merge tag 'mac80211-for-net-2021-12-14' of...

Merge tag 'mac80211-for-net-2021-12-14' of git://git.kernel.org/pub/scm/linux/kernel/git/jberg/mac80211

Johannes Berg says:

====================
A fairly large number of fixes this time:
 * fix a station info memory leak on insert collisions
 * a rate control fix for retransmissions
 * two aggregation setup fixes
 * reload current regdomain when reloading database
 * a locking fix in regulatory work
 * a probe request allocation size fix in mac80211
 * apply TCP vs. aggregation (sk pacing) on mesh
 * fix ordering of channel context update vs. station
   state
 * set up skb->dev for mesh forwarding properly
 * track QoS data frames only for admission control to
   avoid out-of-bounds read (found by syzbot)
 * validate extended element ID vs. existing data to
   avoid out-of-bounds read (found by syzbot)
 * fix locking in mac80211 aggregation TX setup
 * fix traffic stall after HW restart when TXQs are used
 * fix ordering of reconfig/restart after HW restart
 * fix interface type for extended aggregation capability
   lookup
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents a41c4d96 13dee10b
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net> * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2007-2010, Intel Corporation * Copyright 2007-2010, Intel Corporation
* Copyright(c) 2015-2017 Intel Deutschland GmbH * Copyright(c) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018-2020 Intel Corporation * Copyright (C) 2018-2021 Intel Corporation
*/ */
/** /**
...@@ -191,7 +191,8 @@ static void ieee80211_add_addbaext(struct ieee80211_sub_if_data *sdata, ...@@ -191,7 +191,8 @@ static void ieee80211_add_addbaext(struct ieee80211_sub_if_data *sdata,
sband = ieee80211_get_sband(sdata); sband = ieee80211_get_sband(sdata);
if (!sband) if (!sband)
return; return;
he_cap = ieee80211_get_he_iftype_cap(sband, sdata->vif.type); he_cap = ieee80211_get_he_iftype_cap(sband,
ieee80211_vif_type_p2p(&sdata->vif));
if (!he_cap) if (!he_cap)
return; return;
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
* Copyright 2007, Michael Wu <flamingice@sourmilk.net> * Copyright 2007, Michael Wu <flamingice@sourmilk.net>
* Copyright 2007-2010, Intel Corporation * Copyright 2007-2010, Intel Corporation
* Copyright(c) 2015-2017 Intel Deutschland GmbH * Copyright(c) 2015-2017 Intel Deutschland GmbH
* Copyright (C) 2018 - 2020 Intel Corporation * Copyright (C) 2018 - 2021 Intel Corporation
*/ */
#include <linux/ieee80211.h> #include <linux/ieee80211.h>
...@@ -106,7 +106,7 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata, ...@@ -106,7 +106,7 @@ static void ieee80211_send_addba_request(struct ieee80211_sub_if_data *sdata,
mgmt->u.action.u.addba_req.start_seq_num = mgmt->u.action.u.addba_req.start_seq_num =
cpu_to_le16(start_seq_num << 4); cpu_to_le16(start_seq_num << 4);
ieee80211_tx_skb(sdata, skb); ieee80211_tx_skb_tid(sdata, skb, tid);
} }
void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn) void ieee80211_send_bar(struct ieee80211_vif *vif, u8 *ra, u16 tid, u16 ssn)
...@@ -213,6 +213,8 @@ ieee80211_agg_start_txq(struct sta_info *sta, int tid, bool enable) ...@@ -213,6 +213,8 @@ ieee80211_agg_start_txq(struct sta_info *sta, int tid, bool enable)
struct ieee80211_txq *txq = sta->sta.txq[tid]; struct ieee80211_txq *txq = sta->sta.txq[tid];
struct txq_info *txqi; struct txq_info *txqi;
lockdep_assert_held(&sta->ampdu_mlme.mtx);
if (!txq) if (!txq)
return; return;
...@@ -290,7 +292,6 @@ static void ieee80211_remove_tid_tx(struct sta_info *sta, int tid) ...@@ -290,7 +292,6 @@ static void ieee80211_remove_tid_tx(struct sta_info *sta, int tid)
ieee80211_assign_tid_tx(sta, tid, NULL); ieee80211_assign_tid_tx(sta, tid, NULL);
ieee80211_agg_splice_finish(sta->sdata, tid); ieee80211_agg_splice_finish(sta->sdata, tid);
ieee80211_agg_start_txq(sta, tid, false);
kfree_rcu(tid_tx, rcu_head); kfree_rcu(tid_tx, rcu_head);
} }
...@@ -480,8 +481,7 @@ static void ieee80211_send_addba_with_timeout(struct sta_info *sta, ...@@ -480,8 +481,7 @@ static void ieee80211_send_addba_with_timeout(struct sta_info *sta,
/* send AddBA request */ /* send AddBA request */
ieee80211_send_addba_request(sdata, sta->sta.addr, tid, ieee80211_send_addba_request(sdata, sta->sta.addr, tid,
tid_tx->dialog_token, tid_tx->dialog_token, tid_tx->ssn,
sta->tid_seq[tid] >> 4,
buf_size, tid_tx->timeout); buf_size, tid_tx->timeout);
WARN_ON(test_and_set_bit(HT_AGG_STATE_SENT_ADDBA, &tid_tx->state)); WARN_ON(test_and_set_bit(HT_AGG_STATE_SENT_ADDBA, &tid_tx->state));
...@@ -523,6 +523,7 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid) ...@@ -523,6 +523,7 @@ void ieee80211_tx_ba_session_handle_start(struct sta_info *sta, int tid)
params.ssn = sta->tid_seq[tid] >> 4; params.ssn = sta->tid_seq[tid] >> 4;
ret = drv_ampdu_action(local, sdata, &params); ret = drv_ampdu_action(local, sdata, &params);
tid_tx->ssn = params.ssn;
if (ret == IEEE80211_AMPDU_TX_START_DELAY_ADDBA) { if (ret == IEEE80211_AMPDU_TX_START_DELAY_ADDBA) {
return; return;
} else if (ret == IEEE80211_AMPDU_TX_START_IMMEDIATE) { } else if (ret == IEEE80211_AMPDU_TX_START_IMMEDIATE) {
...@@ -889,6 +890,7 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, ...@@ -889,6 +890,7 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid,
{ {
struct ieee80211_sub_if_data *sdata = sta->sdata; struct ieee80211_sub_if_data *sdata = sta->sdata;
bool send_delba = false; bool send_delba = false;
bool start_txq = false;
ht_dbg(sdata, "Stopping Tx BA session for %pM tid %d\n", ht_dbg(sdata, "Stopping Tx BA session for %pM tid %d\n",
sta->sta.addr, tid); sta->sta.addr, tid);
...@@ -906,10 +908,14 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid, ...@@ -906,10 +908,14 @@ void ieee80211_stop_tx_ba_cb(struct sta_info *sta, int tid,
send_delba = true; send_delba = true;
ieee80211_remove_tid_tx(sta, tid); ieee80211_remove_tid_tx(sta, tid);
start_txq = true;
unlock_sta: unlock_sta:
spin_unlock_bh(&sta->lock); spin_unlock_bh(&sta->lock);
if (start_txq)
ieee80211_agg_start_txq(sta, tid, false);
if (send_delba) if (send_delba)
ieee80211_send_delba(sdata, sta->sta.addr, tid, ieee80211_send_delba(sdata, sta->sta.addr, tid,
WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE); WLAN_BACK_INITIATOR, WLAN_REASON_QSTA_NOT_USE);
......
...@@ -1219,8 +1219,11 @@ static inline void drv_wake_tx_queue(struct ieee80211_local *local, ...@@ -1219,8 +1219,11 @@ static inline void drv_wake_tx_queue(struct ieee80211_local *local,
{ {
struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->txq.vif); struct ieee80211_sub_if_data *sdata = vif_to_sdata(txq->txq.vif);
if (local->in_reconfig) /* In reconfig don't transmit now, but mark for waking later */
if (local->in_reconfig) {
set_bit(IEEE80211_TXQ_STOP_NETIF_TX, &txq->flags);
return; return;
}
if (!check_sdata_in_driver(sdata)) if (!check_sdata_in_driver(sdata))
return; return;
......
...@@ -2452,11 +2452,18 @@ static void ieee80211_sta_tx_wmm_ac_notify(struct ieee80211_sub_if_data *sdata, ...@@ -2452,11 +2452,18 @@ static void ieee80211_sta_tx_wmm_ac_notify(struct ieee80211_sub_if_data *sdata,
u16 tx_time) u16 tx_time)
{ {
struct ieee80211_if_managed *ifmgd = &sdata->u.mgd; struct ieee80211_if_managed *ifmgd = &sdata->u.mgd;
u16 tid = ieee80211_get_tid(hdr); u16 tid;
int ac = ieee80211_ac_from_tid(tid); int ac;
struct ieee80211_sta_tx_tspec *tx_tspec = &ifmgd->tx_tspec[ac]; struct ieee80211_sta_tx_tspec *tx_tspec;
unsigned long now = jiffies; unsigned long now = jiffies;
if (!ieee80211_is_data_qos(hdr->frame_control))
return;
tid = ieee80211_get_tid(hdr);
ac = ieee80211_ac_from_tid(tid);
tx_tspec = &ifmgd->tx_tspec[ac];
if (likely(!tx_tspec->admitted_time)) if (likely(!tx_tspec->admitted_time))
return; return;
......
...@@ -2944,6 +2944,7 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx) ...@@ -2944,6 +2944,7 @@ ieee80211_rx_h_mesh_fwding(struct ieee80211_rx_data *rx)
if (!fwd_skb) if (!fwd_skb)
goto out; goto out;
fwd_skb->dev = sdata->dev;
fwd_hdr = (struct ieee80211_hdr *) fwd_skb->data; fwd_hdr = (struct ieee80211_hdr *) fwd_skb->data;
fwd_hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_RETRY); fwd_hdr->frame_control &= ~cpu_to_le16(IEEE80211_FCTL_RETRY);
info = IEEE80211_SKB_CB(fwd_skb); info = IEEE80211_SKB_CB(fwd_skb);
......
...@@ -644,13 +644,13 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) ...@@ -644,13 +644,13 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
/* check if STA exists already */ /* check if STA exists already */
if (sta_info_get_bss(sdata, sta->sta.addr)) { if (sta_info_get_bss(sdata, sta->sta.addr)) {
err = -EEXIST; err = -EEXIST;
goto out_err; goto out_cleanup;
} }
sinfo = kzalloc(sizeof(struct station_info), GFP_KERNEL); sinfo = kzalloc(sizeof(struct station_info), GFP_KERNEL);
if (!sinfo) { if (!sinfo) {
err = -ENOMEM; err = -ENOMEM;
goto out_err; goto out_cleanup;
} }
local->num_sta++; local->num_sta++;
...@@ -667,6 +667,15 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) ...@@ -667,6 +667,15 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
list_add_tail_rcu(&sta->list, &local->sta_list); list_add_tail_rcu(&sta->list, &local->sta_list);
/* update channel context before notifying the driver about state
* change, this enables driver using the updated channel context right away.
*/
if (sta->sta_state >= IEEE80211_STA_ASSOC) {
ieee80211_recalc_min_chandef(sta->sdata);
if (!sta->sta.support_p2p_ps)
ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
}
/* notify driver */ /* notify driver */
err = sta_info_insert_drv_state(local, sdata, sta); err = sta_info_insert_drv_state(local, sdata, sta);
if (err) if (err)
...@@ -674,12 +683,6 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) ...@@ -674,12 +683,6 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
set_sta_flag(sta, WLAN_STA_INSERTED); set_sta_flag(sta, WLAN_STA_INSERTED);
if (sta->sta_state >= IEEE80211_STA_ASSOC) {
ieee80211_recalc_min_chandef(sta->sdata);
if (!sta->sta.support_p2p_ps)
ieee80211_recalc_p2p_go_ps_allowed(sta->sdata);
}
/* accept BA sessions now */ /* accept BA sessions now */
clear_sta_flag(sta, WLAN_STA_BLOCK_BA); clear_sta_flag(sta, WLAN_STA_BLOCK_BA);
...@@ -706,8 +709,8 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU) ...@@ -706,8 +709,8 @@ static int sta_info_insert_finish(struct sta_info *sta) __acquires(RCU)
out_drop_sta: out_drop_sta:
local->num_sta--; local->num_sta--;
synchronize_net(); synchronize_net();
out_cleanup:
cleanup_single_sta(sta); cleanup_single_sta(sta);
out_err:
mutex_unlock(&local->sta_mtx); mutex_unlock(&local->sta_mtx);
kfree(sinfo); kfree(sinfo);
rcu_read_lock(); rcu_read_lock();
......
...@@ -176,6 +176,7 @@ struct sta_info; ...@@ -176,6 +176,7 @@ struct sta_info;
* @failed_bar_ssn: ssn of the last failed BAR tx attempt * @failed_bar_ssn: ssn of the last failed BAR tx attempt
* @bar_pending: BAR needs to be re-sent * @bar_pending: BAR needs to be re-sent
* @amsdu: support A-MSDU withing A-MDPU * @amsdu: support A-MSDU withing A-MDPU
* @ssn: starting sequence number of the session
* *
* This structure's lifetime is managed by RCU, assignments to * This structure's lifetime is managed by RCU, assignments to
* the array holding it must hold the aggregation mutex. * the array holding it must hold the aggregation mutex.
...@@ -199,6 +200,7 @@ struct tid_ampdu_tx { ...@@ -199,6 +200,7 @@ struct tid_ampdu_tx {
u8 stop_initiator; u8 stop_initiator;
bool tx_stop; bool tx_stop;
u16 buf_size; u16 buf_size;
u16 ssn;
u16 failed_bar_ssn; u16 failed_bar_ssn;
bool bar_pending; bool bar_pending;
......
...@@ -1822,15 +1822,15 @@ static int invoke_tx_handlers_late(struct ieee80211_tx_data *tx) ...@@ -1822,15 +1822,15 @@ static int invoke_tx_handlers_late(struct ieee80211_tx_data *tx)
struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb); struct ieee80211_tx_info *info = IEEE80211_SKB_CB(tx->skb);
ieee80211_tx_result res = TX_CONTINUE; ieee80211_tx_result res = TX_CONTINUE;
if (!ieee80211_hw_check(&tx->local->hw, HAS_RATE_CONTROL))
CALL_TXH(ieee80211_tx_h_rate_ctrl);
if (unlikely(info->flags & IEEE80211_TX_INTFL_RETRANSMISSION)) { if (unlikely(info->flags & IEEE80211_TX_INTFL_RETRANSMISSION)) {
__skb_queue_tail(&tx->skbs, tx->skb); __skb_queue_tail(&tx->skbs, tx->skb);
tx->skb = NULL; tx->skb = NULL;
goto txh_done; goto txh_done;
} }
if (!ieee80211_hw_check(&tx->local->hw, HAS_RATE_CONTROL))
CALL_TXH(ieee80211_tx_h_rate_ctrl);
CALL_TXH(ieee80211_tx_h_michael_mic_add); CALL_TXH(ieee80211_tx_h_michael_mic_add);
CALL_TXH(ieee80211_tx_h_sequence); CALL_TXH(ieee80211_tx_h_sequence);
CALL_TXH(ieee80211_tx_h_fragment); CALL_TXH(ieee80211_tx_h_fragment);
...@@ -4191,11 +4191,11 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb, ...@@ -4191,11 +4191,11 @@ void __ieee80211_subif_start_xmit(struct sk_buff *skb,
ieee80211_aggr_check(sdata, sta, skb); ieee80211_aggr_check(sdata, sta, skb);
sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
if (sta) { if (sta) {
struct ieee80211_fast_tx *fast_tx; struct ieee80211_fast_tx *fast_tx;
sk_pacing_shift_update(skb->sk, sdata->local->hw.tx_sk_pacing_shift);
fast_tx = rcu_dereference(sta->fast_tx); fast_tx = rcu_dereference(sta->fast_tx);
if (fast_tx && if (fast_tx &&
......
...@@ -943,7 +943,12 @@ static void ieee80211_parse_extension_element(u32 *crc, ...@@ -943,7 +943,12 @@ static void ieee80211_parse_extension_element(u32 *crc,
struct ieee802_11_elems *elems) struct ieee802_11_elems *elems)
{ {
const void *data = elem->data + 1; const void *data = elem->data + 1;
u8 len = elem->datalen - 1; u8 len;
if (!elem->datalen)
return;
len = elem->datalen - 1;
switch (elem->data[0]) { switch (elem->data[0]) {
case WLAN_EID_EXT_HE_MU_EDCA: case WLAN_EID_EXT_HE_MU_EDCA:
...@@ -2063,7 +2068,7 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata, ...@@ -2063,7 +2068,7 @@ struct sk_buff *ieee80211_build_probe_req(struct ieee80211_sub_if_data *sdata,
chandef.chan = chan; chandef.chan = chan;
skb = ieee80211_probereq_get(&local->hw, src, ssid, ssid_len, skb = ieee80211_probereq_get(&local->hw, src, ssid, ssid_len,
100 + ie_len); local->scan_ies_len + ie_len);
if (!skb) if (!skb)
return NULL; return NULL;
...@@ -2646,6 +2651,13 @@ int ieee80211_reconfig(struct ieee80211_local *local) ...@@ -2646,6 +2651,13 @@ int ieee80211_reconfig(struct ieee80211_local *local)
mutex_unlock(&local->sta_mtx); mutex_unlock(&local->sta_mtx);
} }
/*
* If this is for hw restart things are still running.
* We may want to change that later, however.
*/
if (local->open_count && (!suspended || reconfig_due_to_wowlan))
drv_reconfig_complete(local, IEEE80211_RECONFIG_TYPE_RESTART);
if (local->in_reconfig) { if (local->in_reconfig) {
local->in_reconfig = false; local->in_reconfig = false;
barrier(); barrier();
...@@ -2664,13 +2676,6 @@ int ieee80211_reconfig(struct ieee80211_local *local) ...@@ -2664,13 +2676,6 @@ int ieee80211_reconfig(struct ieee80211_local *local)
IEEE80211_QUEUE_STOP_REASON_SUSPEND, IEEE80211_QUEUE_STOP_REASON_SUSPEND,
false); false);
/*
* If this is for hw restart things are still running.
* We may want to change that later, however.
*/
if (local->open_count && (!suspended || reconfig_due_to_wowlan))
drv_reconfig_complete(local, IEEE80211_RECONFIG_TYPE_RESTART);
if (!suspended) if (!suspended)
return 0; return 0;
......
...@@ -133,6 +133,7 @@ static u32 reg_is_indoor_portid; ...@@ -133,6 +133,7 @@ static u32 reg_is_indoor_portid;
static void restore_regulatory_settings(bool reset_user, bool cached); static void restore_regulatory_settings(bool reset_user, bool cached);
static void print_regdomain(const struct ieee80211_regdomain *rd); static void print_regdomain(const struct ieee80211_regdomain *rd);
static void reg_process_hint(struct regulatory_request *reg_request);
static const struct ieee80211_regdomain *get_cfg80211_regdom(void) static const struct ieee80211_regdomain *get_cfg80211_regdom(void)
{ {
...@@ -1098,6 +1099,8 @@ int reg_reload_regdb(void) ...@@ -1098,6 +1099,8 @@ int reg_reload_regdb(void)
const struct firmware *fw; const struct firmware *fw;
void *db; void *db;
int err; int err;
const struct ieee80211_regdomain *current_regdomain;
struct regulatory_request *request;
err = request_firmware(&fw, "regulatory.db", &reg_pdev->dev); err = request_firmware(&fw, "regulatory.db", &reg_pdev->dev);
if (err) if (err)
...@@ -1118,8 +1121,26 @@ int reg_reload_regdb(void) ...@@ -1118,8 +1121,26 @@ int reg_reload_regdb(void)
if (!IS_ERR_OR_NULL(regdb)) if (!IS_ERR_OR_NULL(regdb))
kfree(regdb); kfree(regdb);
regdb = db; regdb = db;
rtnl_unlock();
/* reset regulatory domain */
current_regdomain = get_cfg80211_regdom();
request = kzalloc(sizeof(*request), GFP_KERNEL);
if (!request) {
err = -ENOMEM;
goto out_unlock;
}
request->wiphy_idx = WIPHY_IDX_INVALID;
request->alpha2[0] = current_regdomain->alpha2[0];
request->alpha2[1] = current_regdomain->alpha2[1];
request->initiator = NL80211_REGDOM_SET_BY_CORE;
request->user_reg_hint_type = NL80211_USER_REG_HINT_USER;
reg_process_hint(request);
out_unlock:
rtnl_unlock();
out: out:
release_firmware(fw); release_firmware(fw);
return err; return err;
...@@ -2338,6 +2359,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) ...@@ -2338,6 +2359,7 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
struct cfg80211_chan_def chandef = {}; struct cfg80211_chan_def chandef = {};
struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy); struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
enum nl80211_iftype iftype; enum nl80211_iftype iftype;
bool ret;
wdev_lock(wdev); wdev_lock(wdev);
iftype = wdev->iftype; iftype = wdev->iftype;
...@@ -2387,7 +2409,11 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev) ...@@ -2387,7 +2409,11 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
case NL80211_IFTYPE_AP: case NL80211_IFTYPE_AP:
case NL80211_IFTYPE_P2P_GO: case NL80211_IFTYPE_P2P_GO:
case NL80211_IFTYPE_ADHOC: case NL80211_IFTYPE_ADHOC:
return cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype); wiphy_lock(wiphy);
ret = cfg80211_reg_can_beacon_relax(wiphy, &chandef, iftype);
wiphy_unlock(wiphy);
return ret;
case NL80211_IFTYPE_STATION: case NL80211_IFTYPE_STATION:
case NL80211_IFTYPE_P2P_CLIENT: case NL80211_IFTYPE_P2P_CLIENT:
return cfg80211_chandef_usable(wiphy, &chandef, return cfg80211_chandef_usable(wiphy, &chandef,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment