]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: cfg80211: add option for vif allowed radios
authorFelix Fietkau <nbd@nbd.name>
Wed, 9 Oct 2024 08:25:42 +0000 (10:25 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 23 Oct 2024 14:44:10 +0000 (16:44 +0200)
This allows users to prevent a vif from affecting radios other than the
configured ones. This can be useful in cases where e.g. an AP is running
on one radio, and triggering a scan on another radio should not disturb it.

Changing the allowed radios list for a vif is supported, but only while
it is down.

While it is possible to achieve the same by always explicitly specifying
a frequency list for scan requests and ensuring that the wrong channel/band
is never accidentally set on an unrelated interface, this change makes
multi-radio wiphy setups a lot easier to deal with for CLI users.

By itself, this patch only enforces the radio mask for scanning requests
and remain-on-channel. Follow-up changes build on this to limit configured
frequencies.

Signed-off-by: Felix Fietkau <nbd@nbd.name>
Link: https://patch.msgid.link/eefcb218780f71a1549875d149f1196486762756.1728462320.git-series.nbd@nbd.name
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/wireless/core.c
net/wireless/nl80211.c
net/wireless/scan.c
net/wireless/util.c

index c8ce5c2e14f4d7cdcb618873b45a6715286d615a..95d05e67e69aa653ce3b31bec70e02690464a4b3 100644 (file)
@@ -6221,6 +6221,7 @@ enum ieee80211_ap_reg_power {
  *     entered.
  * @links.cac_time_ms: CAC time in ms
  * @valid_links: bitmap describing what elements of @links are valid
+ * @radio_mask: Bitmask of radios that this interface is allowed to operate on.
  */
 struct wireless_dev {
        struct wiphy *wiphy;
@@ -6333,6 +6334,8 @@ struct wireless_dev {
                unsigned int cac_time_ms;
        } links[IEEE80211_MLD_MAX_NUM_LINKS];
        u16 valid_links;
+
+       u32 radio_mask;
 };
 
 static inline const u8 *wdev_address(struct wireless_dev *wdev)
@@ -6518,6 +6521,17 @@ static inline bool cfg80211_channel_is_psc(struct ieee80211_channel *chan)
 bool cfg80211_radio_chandef_valid(const struct wiphy_radio *radio,
                                  const struct cfg80211_chan_def *chandef);
 
+/**
+ * cfg80211_wdev_channel_allowed - Check if the wdev may use the channel
+ *
+ * @wdev: the wireless device
+ * @chan: channel to check
+ *
+ * Return: whether or not the wdev may use the channel
+ */
+bool cfg80211_wdev_channel_allowed(struct wireless_dev *wdev,
+                                  struct ieee80211_channel *chan);
+
 /**
  * ieee80211_get_response_rate - get basic rate for a given rate
  *
index f97f5adc8d51868e7d6329c1ac17f107be0c413e..d31ccee99cc7652c4a095f7be725dfd3c66343b3 100644 (file)
@@ -2868,6 +2868,9 @@ enum nl80211_commands {
  *     nested item, it contains attributes defined in
  *     &enum nl80211_if_combination_attrs.
  *
+ * @NL80211_ATTR_VIF_RADIO_MASK: Bitmask of allowed radios (u32).
+ *     A value of 0 means all radios.
+ *
  * @NUM_NL80211_ATTR: total number of nl80211_attrs available
  * @NL80211_ATTR_MAX: highest attribute number currently defined
  * @__NL80211_ATTR_AFTER_LAST: internal use
@@ -3416,6 +3419,8 @@ enum nl80211_attrs {
        NL80211_ATTR_WIPHY_RADIOS,
        NL80211_ATTR_WIPHY_INTERFACE_COMBINATIONS,
 
+       NL80211_ATTR_VIF_RADIO_MASK,
+
        /* add attributes here, update the policy in nl80211.c */
 
        __NL80211_ATTR_AFTER_LAST,
index 4c8d8f167409c6c2aacba3352db993744b85746b..93d62a1d3a4560afc71723a02c92cf6aac0bf848 100644 (file)
@@ -1430,6 +1430,8 @@ void cfg80211_init_wdev(struct wireless_dev *wdev)
        /* allow mac80211 to determine the timeout */
        wdev->ps_timeout = -1;
 
+       wdev->radio_mask = BIT(wdev->wiphy->n_radio) - 1;
+
        if ((wdev->iftype == NL80211_IFTYPE_STATION ||
             wdev->iftype == NL80211_IFTYPE_P2P_CLIENT ||
             wdev->iftype == NL80211_IFTYPE_ADHOC) && !wdev->use_4addr)
index fb35c03af34c4a867ad3e9d2379a9fffdecc42c3..a330347dd7a38c1b9ee38c2389f98bab32ca767f 100644 (file)
@@ -829,6 +829,7 @@ static const struct nla_policy nl80211_policy[NUM_NL80211_ATTR] = {
        [NL80211_ATTR_MLO_TTLM_DLINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8),
        [NL80211_ATTR_MLO_TTLM_ULINK] = NLA_POLICY_EXACT_LEN(sizeof(u16) * 8),
        [NL80211_ATTR_ASSOC_SPP_AMSDU] = { .type = NLA_FLAG },
+       [NL80211_ATTR_VIF_RADIO_MASK] = { .type = NLA_U32 },
 };
 
 /* policy for the key attributes */
@@ -3996,7 +3997,8 @@ static int nl80211_send_iface(struct sk_buff *msg, u32 portid, u32 seq, int flag
            nla_put_u32(msg, NL80211_ATTR_GENERATION,
                        rdev->devlist_generation ^
                        (cfg80211_rdev_list_generation << 2)) ||
-           nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr))
+           nla_put_u8(msg, NL80211_ATTR_4ADDR, wdev->use_4addr) ||
+           nla_put_u32(msg, NL80211_ATTR_VIF_RADIO_MASK, wdev->radio_mask))
                goto nla_put_failure;
 
        if (rdev->ops->get_channel && !wdev->valid_links) {
@@ -4312,6 +4314,29 @@ static int nl80211_valid_4addr(struct cfg80211_registered_device *rdev,
        return -EOPNOTSUPP;
 }
 
+static int nl80211_parse_vif_radio_mask(struct genl_info *info,
+                                       u32 *radio_mask)
+{
+       struct cfg80211_registered_device *rdev = info->user_ptr[0];
+       struct nlattr *attr = info->attrs[NL80211_ATTR_VIF_RADIO_MASK];
+       u32 mask, allowed;
+
+       if (!attr) {
+               *radio_mask = 0;
+               return 0;
+       }
+
+       allowed = BIT(rdev->wiphy.n_radio) - 1;
+       mask = nla_get_u32(attr);
+       if (mask & ~allowed)
+               return -EINVAL;
+       if (!mask)
+               mask = allowed;
+       *radio_mask = mask;
+
+       return 1;
+}
+
 static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
 {
        struct cfg80211_registered_device *rdev = info->user_ptr[0];
@@ -4319,6 +4344,8 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        int err;
        enum nl80211_iftype otype, ntype;
        struct net_device *dev = info->user_ptr[1];
+       struct wireless_dev *wdev = dev->ieee80211_ptr;
+       u32 radio_mask = 0;
        bool change = false;
 
        memset(&params, 0, sizeof(params));
@@ -4332,8 +4359,6 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        }
 
        if (info->attrs[NL80211_ATTR_MESH_ID]) {
-               struct wireless_dev *wdev = dev->ieee80211_ptr;
-
                if (ntype != NL80211_IFTYPE_MESH_POINT)
                        return -EINVAL;
                if (otype != NL80211_IFTYPE_MESH_POINT)
@@ -4364,6 +4389,12 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        if (err > 0)
                change = true;
 
+       err = nl80211_parse_vif_radio_mask(info, &radio_mask);
+       if (err < 0)
+               return err;
+       if (err && netif_running(dev))
+               return -EBUSY;
+
        if (change)
                err = cfg80211_change_iface(rdev, dev, ntype, &params);
        else
@@ -4372,11 +4403,11 @@ static int nl80211_set_interface(struct sk_buff *skb, struct genl_info *info)
        if (!err && params.use_4addr != -1)
                dev->ieee80211_ptr->use_4addr = params.use_4addr;
 
-       if (change && !err) {
-               struct wireless_dev *wdev = dev->ieee80211_ptr;
+       if (radio_mask)
+               wdev->radio_mask = radio_mask;
 
+       if (change && !err)
                nl80211_notify_iface(rdev, wdev, NL80211_CMD_SET_INTERFACE);
-       }
 
        return err;
 }
@@ -4387,6 +4418,7 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
        struct vif_params params;
        struct wireless_dev *wdev;
        struct sk_buff *msg;
+       u32 radio_mask;
        int err;
        enum nl80211_iftype type = NL80211_IFTYPE_UNSPECIFIED;
 
@@ -4424,6 +4456,10 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
        if (err < 0)
                return err;
 
+       err = nl80211_parse_vif_radio_mask(info, &radio_mask);
+       if (err < 0)
+               return err;
+
        msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return -ENOMEM;
@@ -4465,6 +4501,9 @@ static int _nl80211_new_interface(struct sk_buff *skb, struct genl_info *info)
                break;
        }
 
+       if (radio_mask)
+               wdev->radio_mask = radio_mask;
+
        if (nl80211_send_iface(msg, info->snd_portid, info->snd_seq, 0,
                               rdev, wdev, NL80211_CMD_NEW_INTERFACE) < 0) {
                nlmsg_free(msg);
@@ -9156,6 +9195,9 @@ static bool cfg80211_off_channel_oper_allowed(struct wireless_dev *wdev,
 
        lockdep_assert_wiphy(wdev->wiphy);
 
+       if (!cfg80211_wdev_channel_allowed(wdev, chan))
+               return false;
+
        if (!cfg80211_beaconing_iface_active(wdev))
                return true;
 
@@ -9368,7 +9410,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
                        }
 
                        /* ignore disabled channels */
-                       if (chan->flags & IEEE80211_CHAN_DISABLED)
+                       if (chan->flags & IEEE80211_CHAN_DISABLED ||
+                           !cfg80211_wdev_channel_allowed(wdev, chan))
                                continue;
 
                        request->channels[i] = chan;
@@ -9388,7 +9431,8 @@ static int nl80211_trigger_scan(struct sk_buff *skb, struct genl_info *info)
 
                                chan = &wiphy->bands[band]->channels[j];
 
-                               if (chan->flags & IEEE80211_CHAN_DISABLED)
+                               if (chan->flags & IEEE80211_CHAN_DISABLED ||
+                                   !cfg80211_wdev_channel_allowed(wdev, chan))
                                        continue;
 
                                request->channels[i] = chan;
index 8ba618f4734f756af0c8ce965df6343102757d0c..8e3d46bf483610eacd6f8adc5b806622374469ce 100644 (file)
@@ -956,7 +956,8 @@ static int cfg80211_scan_6ghz(struct cfg80211_registered_device *rdev)
                struct ieee80211_channel *chan =
                        ieee80211_get_channel(&rdev->wiphy, ap->center_freq);
 
-               if (!chan || chan->flags & IEEE80211_CHAN_DISABLED)
+               if (!chan || chan->flags & IEEE80211_CHAN_DISABLED ||
+                   !cfg80211_wdev_channel_allowed(rdev_req->wdev, chan))
                        continue;
 
                for (i = 0; i < rdev_req->n_channels; i++) {
@@ -3515,9 +3516,12 @@ int cfg80211_wext_siwscan(struct net_device *dev,
                        continue;
 
                for (j = 0; j < wiphy->bands[band]->n_channels; j++) {
+                       struct ieee80211_channel *chan;
+
                        /* ignore disabled channels */
-                       if (wiphy->bands[band]->channels[j].flags &
-                                               IEEE80211_CHAN_DISABLED)
+                       chan = &wiphy->bands[band]->channels[j];
+                       if (chan->flags & IEEE80211_CHAN_DISABLED ||
+                           !cfg80211_wdev_channel_allowed(creq->wdev, chan))
                                continue;
 
                        /* If we have a wireless request structure and the
index 93a9c32418a6ccb5e950a3609a3c6d25df4af3ac..040d62051eb96ea52ba301f0767d2e4e4ba51e0b 100644 (file)
@@ -2923,3 +2923,32 @@ bool cfg80211_radio_chandef_valid(const struct wiphy_radio *radio,
        return true;
 }
 EXPORT_SYMBOL(cfg80211_radio_chandef_valid);
+
+bool cfg80211_wdev_channel_allowed(struct wireless_dev *wdev,
+                                  struct ieee80211_channel *chan)
+{
+       struct wiphy *wiphy = wdev->wiphy;
+       const struct wiphy_radio *radio;
+       struct cfg80211_chan_def chandef;
+       u32 radio_mask;
+       int i;
+
+       radio_mask = wdev->radio_mask;
+       if (!wiphy->n_radio || radio_mask == BIT(wiphy->n_radio) - 1)
+               return true;
+
+       cfg80211_chandef_create(&chandef, chan, NL80211_CHAN_HT20);
+       for (i = 0; i < wiphy->n_radio; i++) {
+               if (!(radio_mask & BIT(i)))
+                       continue;
+
+               radio = &wiphy->radio[i];
+               if (!cfg80211_radio_chandef_valid(radio, &chandef))
+                       continue;
+
+               return true;
+       }
+
+       return false;
+}
+EXPORT_SYMBOL(cfg80211_wdev_channel_allowed);