]> git.ipfire.org Git - thirdparty/kernel/linux.git/commitdiff
wifi: cfg80211: add support for NAN data interface
authorMiri Korenblit <miriam.rachel.korenblit@intel.com>
Wed, 18 Mar 2026 12:39:17 +0000 (14:39 +0200)
committerJohannes Berg <johannes.berg@intel.com>
Wed, 25 Mar 2026 19:56:53 +0000 (20:56 +0100)
This new interface type represents a NAN data interface (NDI).
It is used for data communication with NAN peers.

Note that the existing NL80211_IFTYPE_NAN interface, which is the NAN
Management Interface (NMI), is used for management communication.

An NDI interface is started when a new NAN data path is about to
be established, and is stopped after the NAN data path is terminated.

- An NDI interface can only be started if the NMI is running, and NAN is
  started.
- Before the NMI is stopped, the NDI interfaces will be stopped.

Add the new interface type, handle add/remove operations for it,
and makes sure of the conditions above.

Signed-off-by: Miri Korenblit <miriam.rachel.korenblit@intel.com>
Link: https://patch.msgid.link/20260219114327.0d681335c2e2.I92973483e927820ae2297853c141842fdb262747@changeid
Link: https://patch.msgid.link/20260318123926.206536-4-miriam.rachel.korenblit@intel.com
Signed-off-by: Johannes Berg <johannes.berg@intel.com>
14 files changed:
include/net/cfg80211.h
include/uapi/linux/nl80211.h
net/mac80211/cfg.c
net/mac80211/chan.c
net/mac80211/iface.c
net/mac80211/rx.c
net/mac80211/util.c
net/wireless/chan.c
net/wireless/core.c
net/wireless/core.h
net/wireless/nl80211.c
net/wireless/reg.c
net/wireless/sysfs.c
net/wireless/util.c

index 539dcf65c188dcaac2c0dd436d1cc84c9d6b59df..1797ece502955d41a21b2590963f6effb0c457d8 100644 (file)
@@ -3980,6 +3980,27 @@ struct cfg80211_qos_map {
        struct cfg80211_dscp_range up[8];
 };
 
+/**
+ * DOC: Neighbor Awareness Networking (NAN)
+ *
+ * NAN uses two interface types:
+ *
+ * - %NL80211_IFTYPE_NAN: a non-netdev interface. This has two roles: (1) holds
+ *   the configuration of all NAN activities (DE parameters, synchronisation
+ *   parameters, local schedule, etc.), and (2) uses as the NAN Management
+ *   Interface (NMI), which is used for NAN management communication.
+ *
+ * - %NL80211_IFTYPE_NAN_DATA: The NAN Data Interface (NDI), used for data
+ *   communication with NAN peers.
+ *
+ * An NDI interface can only be started (IFF_UP) if the NMI one is running and
+ * NAN is started. Before NAN is stopped, all associated NDI interfaces
+ * must be stopped first.
+ *
+ * The local schedule specifies which channels the device is available on and
+ * when. Must be cancelled before NAN is stopped.
+ */
+
 /**
  * struct cfg80211_nan_band_config - NAN band specific configuration
  *
index 484094667abc3c9ae2b6f9fc8946555e797709a2..3984c176f9e763417e14a3838ae61e7dd0c59eeb 100644 (file)
@@ -3749,6 +3749,9 @@ enum nl80211_attrs {
  * @NL80211_IFTYPE_OCB: Outside Context of a BSS
  *     This mode corresponds to the MIB variable dot11OCBActivated=true
  * @NL80211_IFTYPE_NAN: NAN device interface type (not a netdev)
+ * @NL80211_IFTYPE_NAN_DATA: NAN data interface type (netdev); NAN data
+ *     interfaces can only be brought up (IFF_UP) when a NAN interface
+ *     already exists and NAN has been started (using %NL80211_CMD_START_NAN).
  * @NL80211_IFTYPE_MAX: highest interface type number currently defined
  * @NUM_NL80211_IFTYPES: number of defined interface types
  *
@@ -3770,6 +3773,7 @@ enum nl80211_iftype {
        NL80211_IFTYPE_P2P_DEVICE,
        NL80211_IFTYPE_OCB,
        NL80211_IFTYPE_NAN,
+       NL80211_IFTYPE_NAN_DATA,
 
        /* keep last */
        NUM_NL80211_IFTYPES,
index 9aa4ae0621be5d4a6bd6869d4938b179c878f6a0..13132215afb492a9da41f4b4ab3ebba951b17abd 100644 (file)
@@ -718,6 +718,7 @@ static int ieee80211_add_key(struct wiphy *wiphy, struct wireless_dev *wdev,
        case NL80211_IFTYPE_P2P_CLIENT:
        case NL80211_IFTYPE_P2P_GO:
        case NL80211_IFTYPE_OCB:
+       case NL80211_IFTYPE_NAN_DATA:
                /* shouldn't happen */
                WARN_ON_ONCE(1);
                break;
index bc396d6c64c53b3afc50229d7f07cd8a208a9cef..1e4bfcd2569792d32451fae18db570ad4ce6f5b5 100644 (file)
@@ -495,6 +495,7 @@ ieee80211_get_width_of_link(struct ieee80211_link_data *link)
        case NUM_NL80211_IFTYPES:
        case NL80211_IFTYPE_P2P_CLIENT:
        case NL80211_IFTYPE_P2P_GO:
+       case NL80211_IFTYPE_NAN_DATA:
                WARN_ON_ONCE(1);
                break;
        }
@@ -1458,6 +1459,7 @@ ieee80211_link_chanctx_reservation_complete(struct ieee80211_link_data *link)
        case NL80211_IFTYPE_P2P_GO:
        case NL80211_IFTYPE_P2P_DEVICE:
        case NL80211_IFTYPE_NAN:
+       case NL80211_IFTYPE_NAN_DATA:
        case NUM_NL80211_IFTYPES:
                WARN_ON(1);
                break;
index 234de4762be52916969f7319977e0e4b5fd277be..125897717a4c9290bd4d0bbb097c5383361e521d 100644 (file)
@@ -1368,6 +1368,7 @@ int ieee80211_do_open(struct wireless_dev *wdev, bool coming_up)
        case NL80211_IFTYPE_P2P_DEVICE:
        case NL80211_IFTYPE_OCB:
        case NL80211_IFTYPE_NAN:
+       case NL80211_IFTYPE_NAN_DATA:
                /* no special treatment */
                break;
        case NL80211_IFTYPE_UNSPECIFIED:
@@ -1945,6 +1946,8 @@ static void ieee80211_setup_sdata(struct ieee80211_sub_if_data *sdata,
        case NL80211_IFTYPE_P2P_DEVICE:
                sdata->vif.bss_conf.bssid = sdata->vif.addr;
                break;
+       case NL80211_IFTYPE_NAN_DATA:
+               break;
        case NL80211_IFTYPE_UNSPECIFIED:
        case NL80211_IFTYPE_WDS:
        case NUM_NL80211_IFTYPES:
index 19c33f7a819343e3e238e4541a4a172cfb36637b..d9a654ef082d11fdd117a7d3540618c4893afe13 100644 (file)
@@ -4607,6 +4607,8 @@ static bool ieee80211_accept_frame(struct ieee80211_rx_data *rx)
                        (ieee80211_is_public_action(hdr, skb->len) ||
                         (ieee80211_is_auth(hdr->frame_control) &&
                          ether_addr_equal(sdata->vif.addr, hdr->addr1)));
+       case NL80211_IFTYPE_NAN_DATA:
+               return false;
        default:
                break;
        }
index 55054de62508bee9c3104a53c070cc6cca9c51b1..8987a450452002859d9cf811d774d1305cc308ed 100644 (file)
@@ -2118,6 +2118,7 @@ int ieee80211_reconfig(struct ieee80211_local *local)
                                return res;
                        }
                        break;
+               case NL80211_IFTYPE_NAN_DATA:
                case NL80211_IFTYPE_AP_VLAN:
                case NL80211_IFTYPE_MONITOR:
                case NL80211_IFTYPE_P2P_DEVICE:
index 2dcf18f5655e319e87cc031faa60e8c1cd9b75bc..8b94c0de80adea26af901c58beab1781bfa7a62d 100644 (file)
@@ -816,6 +816,7 @@ int cfg80211_chandef_dfs_required(struct wiphy *wiphy,
        case NL80211_IFTYPE_MONITOR:
        case NL80211_IFTYPE_AP_VLAN:
        case NL80211_IFTYPE_P2P_DEVICE:
+       case NL80211_IFTYPE_NAN_DATA:
                break;
        case NL80211_IFTYPE_WDS:
        case NL80211_IFTYPE_UNSPECIFIED:
@@ -939,6 +940,7 @@ bool cfg80211_beaconing_iface_active(struct wireless_dev *wdev)
        case NL80211_IFTYPE_P2P_DEVICE:
        /* Can NAN type be considered as beaconing interface? */
        case NL80211_IFTYPE_NAN:
+       case NL80211_IFTYPE_NAN_DATA:
                break;
        case NL80211_IFTYPE_UNSPECIFIED:
        case NL80211_IFTYPE_WDS:
index 54c89b0db3528f99c275f939d660cab391f3c9ab..200b97f912eb21aefe9d2626311000bbb370a410 100644 (file)
@@ -329,16 +329,21 @@ void cfg80211_shutdown_all_interfaces(struct wiphy *wiphy)
 
        ASSERT_RTNL();
 
+       /*
+        * Some netdev interfaces need to be closed before some non-netdev
+        * ones, i.e. NAN_DATA interfaces need to be closed before the NAN
+        * interface
+        */
        list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                if (wdev->netdev) {
                        dev_close(wdev->netdev);
                        continue;
                }
+       }
 
-               /* otherwise, check iftype */
-
-               guard(wiphy)(wiphy);
+       guard(wiphy)(wiphy);
 
+       list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
                switch (wdev->iftype) {
                case NL80211_IFTYPE_P2P_DEVICE:
                        cfg80211_stop_p2p_device(rdev, wdev);
@@ -396,6 +401,8 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
 
        list_for_each_entry_safe(wdev, tmp, &rdev->wiphy.wdev_list, list) {
                if (wdev->nl_owner_dead) {
+                       cfg80211_close_dependents(rdev, wdev);
+
                        if (wdev->netdev)
                                dev_close(wdev->netdev);
 
@@ -406,6 +413,21 @@ void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev)
        }
 }
 
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+                              struct wireless_dev *wdev)
+{
+       ASSERT_RTNL();
+
+       if (wdev->iftype != NL80211_IFTYPE_NAN)
+               return;
+
+       /* Close all NAN DATA interfaces */
+       list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+               if (wdev->iftype == NL80211_IFTYPE_NAN_DATA)
+                       dev_close(wdev->netdev);
+       }
+}
+
 static void cfg80211_destroy_iface_wk(struct work_struct *work)
 {
        struct cfg80211_registered_device *rdev;
@@ -1419,9 +1441,8 @@ void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
                rdev->num_running_monitor_ifaces += num;
 }
 
-void cfg80211_leave(struct cfg80211_registered_device *rdev,
-                   struct wireless_dev *wdev,
-                   int link_id)
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+                          struct wireless_dev *wdev, int link_id)
 {
        struct net_device *dev = wdev->netdev;
        struct cfg80211_sched_scan_request *pos, *tmp;
@@ -1472,6 +1493,7 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
                break;
        case NL80211_IFTYPE_AP_VLAN:
        case NL80211_IFTYPE_MONITOR:
+       case NL80211_IFTYPE_NAN_DATA:
                /* nothing to do */
                break;
        case NL80211_IFTYPE_UNSPECIFIED:
@@ -1482,6 +1504,19 @@ void cfg80211_leave(struct cfg80211_registered_device *rdev,
        }
 }
 
+void cfg80211_leave(struct cfg80211_registered_device *rdev,
+                   struct wireless_dev *wdev, int link_id)
+{
+       ASSERT_RTNL();
+
+       /* NAN_DATA interfaces must be closed before stopping NAN */
+       cfg80211_close_dependents(rdev, wdev);
+
+       guard(wiphy)(&rdev->wiphy);
+
+       cfg80211_leave_locked(rdev, wdev, link_id);
+}
+
 void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
                        int link_id, gfp_t gfp)
 {
@@ -1497,6 +1532,9 @@ void cfg80211_stop_link(struct wiphy *wiphy, struct wireless_dev *wdev,
 
        trace_cfg80211_stop_link(wiphy, wdev, link_id);
 
+       if (wdev->iftype == NL80211_IFTYPE_NAN)
+               return;
+
        ev = kzalloc_obj(*ev, gfp);
        if (!ev)
                return;
@@ -1647,10 +1685,9 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
                }
                break;
        case NETDEV_GOING_DOWN:
-               scoped_guard(wiphy, &rdev->wiphy) {
-                       cfg80211_leave(rdev, wdev, -1);
+               cfg80211_leave(rdev, wdev, -1);
+               scoped_guard(wiphy, &rdev->wiphy)
                        cfg80211_remove_links(wdev);
-               }
                /* since we just did cfg80211_leave() nothing to do there */
                cancel_work_sync(&wdev->disconnect_wk);
                cancel_work_sync(&wdev->pmsr_free_wk);
@@ -1731,6 +1768,23 @@ static int cfg80211_netdev_notifier_call(struct notifier_block *nb,
 
                if (rfkill_blocked(rdev->wiphy.rfkill))
                        return notifier_from_errno(-ERFKILL);
+
+               /* NAN_DATA interfaces require a running NAN interface */
+               if (wdev->iftype == NL80211_IFTYPE_NAN_DATA) {
+                       struct wireless_dev *iter;
+                       bool nan_started = false;
+
+                       list_for_each_entry(iter, &rdev->wiphy.wdev_list, list) {
+                               if (iter->iftype == NL80211_IFTYPE_NAN &&
+                                   wdev_running(iter)) {
+                                       nan_started = true;
+                                       break;
+                               }
+                       }
+
+                       if (!nan_started)
+                               return notifier_from_errno(-ENOLINK);
+               }
                break;
        default:
                return NOTIFY_DONE;
index c7ae1f8a9bd814ac7dc5918003fe059d42144f7e..ae2d56d3ad9083e66b13d0483a310c5a8b8771d3 100644 (file)
@@ -318,6 +318,9 @@ void cfg80211_cqm_rssi_notify_work(struct wiphy *wiphy,
 
 void cfg80211_destroy_ifaces(struct cfg80211_registered_device *rdev);
 
+void cfg80211_close_dependents(struct cfg80211_registered_device *rdev,
+                              struct wireless_dev *wdev);
+
 /* free object */
 void cfg80211_dev_free(struct cfg80211_registered_device *rdev);
 
@@ -541,6 +544,9 @@ int cfg80211_validate_beacon_int(struct cfg80211_registered_device *rdev,
 void cfg80211_update_iface_num(struct cfg80211_registered_device *rdev,
                               enum nl80211_iftype iftype, int num);
 
+void cfg80211_leave_locked(struct cfg80211_registered_device *rdev,
+                          struct wireless_dev *wdev, int link_id);
+
 void cfg80211_leave(struct cfg80211_registered_device *rdev,
                    struct wireless_dev *wdev,
                    int link_id);
index de630e0d388b095b0a0e9a72538cad7b411f8856..7cea8fef6ae52ab83974e77bb65e8b026900600b 100644 (file)
@@ -1764,6 +1764,7 @@ static int nl80211_key_allowed(struct wireless_dev *wdev)
                        return 0;
                return -ENOLINK;
        case NL80211_IFTYPE_NAN:
+       case NL80211_IFTYPE_NAN_DATA:
                if (wiphy_ext_feature_isset(wdev->wiphy,
                                            NL80211_EXT_FEATURE_SECURE_NAN))
                        return 0;
@@ -4921,6 +4922,8 @@ static int nl80211_del_interface(struct sk_buff *skb, struct genl_info *info)
        else
                dev_close(wdev->netdev);
 
+       cfg80211_close_dependents(rdev, wdev);
+
        mutex_lock(&rdev->wiphy.mtx);
 
        return cfg80211_remove_virtual_intf(rdev, wdev);
@@ -15964,6 +15967,10 @@ static int nl80211_stop_nan(struct sk_buff *skb, struct genl_info *info)
        if (wdev->iftype != NL80211_IFTYPE_NAN)
                return -EOPNOTSUPP;
 
+       cfg80211_close_dependents(rdev, wdev);
+
+       guard(wiphy)(&rdev->wiphy);
+
        cfg80211_stop_nan(rdev, wdev);
 
        return 0;
@@ -18356,7 +18363,11 @@ nl80211_epcs_cfg(struct sk_buff *skb, struct genl_info *info)
                 NL80211_FLAG_NEED_RTNL)                \
        SELECTOR(__sel, WIPHY_CLEAR,                    \
                 NL80211_FLAG_NEED_WIPHY |              \
-                NL80211_FLAG_CLEAR_SKB)
+                NL80211_FLAG_CLEAR_SKB)                \
+       SELECTOR(__sel, WDEV_UP_RTNL_NOMTX,             \
+                NL80211_FLAG_NEED_WDEV_UP |            \
+                NL80211_FLAG_NO_WIPHY_MTX |            \
+                NL80211_FLAG_NEED_RTNL)
 
 enum nl80211_internal_flags_selector {
 #define SELECTOR(_, name, value)       NL80211_IFL_SEL_##name,
@@ -19193,6 +19204,7 @@ static const struct genl_small_ops nl80211_small_ops[] = {
                .doit = nl80211_stop_nan,
                .flags = GENL_ADMIN_PERM,
                .internal_flags = IFLAGS(NL80211_FLAG_NEED_WDEV_UP |
+                                        NL80211_FLAG_NO_WIPHY_MTX |
                                         NL80211_FLAG_NEED_RTNL),
        },
        {
index 4b5450aec72ed9132e7fd4fb2d66e1af1efd7320..5db2121c0b570b7a1efb510c700c6f8aded5c4e7 100644 (file)
@@ -2409,6 +2409,9 @@ static bool reg_wdev_chan_valid(struct wiphy *wiphy, struct wireless_dev *wdev)
                                continue;
                        chandef = wdev->u.ocb.chandef;
                        break;
+               case NL80211_IFTYPE_NAN_DATA:
+                       /* NAN channels are checked in NL80211_IFTYPE_NAN interface */
+                       break;
                default:
                        /* others not implemented for now */
                        WARN_ON_ONCE(1);
@@ -2445,11 +2448,14 @@ static void reg_leave_invalid_chans(struct wiphy *wiphy)
        struct wireless_dev *wdev;
        struct cfg80211_registered_device *rdev = wiphy_to_rdev(wiphy);
 
-       guard(wiphy)(wiphy);
+       list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list) {
+               bool valid;
 
-       list_for_each_entry(wdev, &rdev->wiphy.wdev_list, list)
-               if (!reg_wdev_chan_valid(wiphy, wdev))
+               scoped_guard(wiphy, wiphy)
+                       valid = reg_wdev_chan_valid(wiphy, wdev);
+               if (!valid)
                        cfg80211_leave(rdev, wdev, -1);
+       }
 }
 
 static void reg_check_chans_work(struct work_struct *work)
index 3385a27468f7a22d0d49d33931237e8c495d02c7..d45ddc457c30519a0e6bce45021b22b53b9e6612 100644 (file)
@@ -102,25 +102,26 @@ static int wiphy_suspend(struct device *dev)
        if (!rdev->wiphy.registered)
                goto out_unlock_rtnl;
 
-       wiphy_lock(&rdev->wiphy);
        if (rdev->wiphy.wowlan_config) {
-               cfg80211_process_wiphy_works(rdev, NULL);
-               if (rdev->ops->suspend)
-                       ret = rdev_suspend(rdev, rdev->wiphy.wowlan_config);
-               if (ret <= 0)
-                       goto out_unlock_wiphy;
+               scoped_guard(wiphy, &rdev->wiphy) {
+                       cfg80211_process_wiphy_works(rdev, NULL);
+                       if (rdev->ops->suspend)
+                               ret = rdev_suspend(rdev,
+                                                  rdev->wiphy.wowlan_config);
+                       if (ret <= 0)
+                               goto out_unlock_rtnl;
+               }
        }
 
        /* Driver refused to configure wowlan (ret = 1) or no wowlan */
 
        cfg80211_leave_all(rdev);
-       cfg80211_process_rdev_events(rdev);
-       cfg80211_process_wiphy_works(rdev, NULL);
-       if (rdev->ops->suspend)
-               ret = rdev_suspend(rdev, NULL);
-
-out_unlock_wiphy:
-       wiphy_unlock(&rdev->wiphy);
+       scoped_guard(wiphy, &rdev->wiphy) {
+               cfg80211_process_rdev_events(rdev);
+               cfg80211_process_wiphy_works(rdev, NULL);
+               if (rdev->ops->suspend)
+                       ret = rdev_suspend(rdev, NULL);
+       }
 out_unlock_rtnl:
        if (ret == 0)
                rdev->suspended = true;
index 1a861a6ea38014d88d2f0635d31494d50ade6a13..e2878d20a32dfe1b4494ac7bb42b575a6c446151 100644 (file)
@@ -1144,8 +1144,15 @@ void cfg80211_process_wdev_events(struct wireless_dev *wdev)
                                               ev->ij.channel);
                        break;
                case EVENT_STOPPED:
-                       cfg80211_leave(wiphy_to_rdev(wdev->wiphy), wdev,
-                                      ev->link_id);
+                       /*
+                        * for NAN interfaces cfg80211_leave must be called but
+                        * locking here doesn't allow this.
+                        */
+                       if (WARN_ON(wdev->iftype == NL80211_IFTYPE_NAN))
+                               break;
+
+                       cfg80211_leave_locked(wiphy_to_rdev(wdev->wiphy), wdev,
+                                             ev->link_id);
                        break;
                case EVENT_PORT_AUTHORIZED:
                        __cfg80211_port_authorized(wdev, ev->pa.peer_addr,
@@ -1184,6 +1191,13 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
        if (otype == NL80211_IFTYPE_AP_VLAN)
                return -EOPNOTSUPP;
 
+       /*
+        * for NAN interfaces cfg80211_leave must be called for leaving,
+        * but locking here doesn't allow this.
+        */
+       if (otype == NL80211_IFTYPE_NAN)
+               return -EOPNOTSUPP;
+
        /* cannot change into P2P device or NAN */
        if (ntype == NL80211_IFTYPE_P2P_DEVICE ||
            ntype == NL80211_IFTYPE_NAN)
@@ -1204,7 +1218,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
                dev->ieee80211_ptr->use_4addr = false;
                rdev_set_qos_map(rdev, dev, NULL);
 
-               cfg80211_leave(rdev, dev->ieee80211_ptr, -1);
+               cfg80211_leave_locked(rdev, dev->ieee80211_ptr, -1);
 
                cfg80211_process_rdev_events(rdev);
                cfg80211_mlme_purge_registrations(dev->ieee80211_ptr);
@@ -1232,6 +1246,7 @@ int cfg80211_change_iface(struct cfg80211_registered_device *rdev,
                case NL80211_IFTYPE_OCB:
                case NL80211_IFTYPE_P2P_CLIENT:
                case NL80211_IFTYPE_ADHOC:
+               case NL80211_IFTYPE_NAN_DATA:
                        dev->priv_flags |= IFF_DONT_BRIDGE;
                        break;
                case NL80211_IFTYPE_P2P_GO: