Linux 6.9-rc1
[linux-2.6-microblaze.git] / net / ieee802154 / nl802154.c
index 1b6a9f2..7eb37de 100644 (file)
@@ -234,6 +234,8 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
                                 NL802154_SCAN_DONE_REASON_ABORTED),
        [NL802154_ATTR_BEACON_INTERVAL] =
                NLA_POLICY_MAX(NLA_U8, IEEE802154_ACTIVE_SCAN_DURATION),
+       [NL802154_ATTR_MAX_ASSOCIATIONS] = { .type = NLA_U32 },
+       [NL802154_ATTR_PEER] = { .type = NLA_NESTED },
 
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
        [NL802154_ATTR_SEC_ENABLED] = { .type = NLA_U8, },
@@ -248,7 +250,6 @@ static const struct nla_policy nl802154_policy[NL802154_ATTR_MAX+1] = {
 #endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
 };
 
-#ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
 static int
 nl802154_prepare_wpan_dev_dump(struct sk_buff *skb,
                               struct netlink_callback *cb,
@@ -307,7 +308,6 @@ nl802154_finish_wpan_dev_dump(struct cfg802154_registered_device *rdev)
 {
        rtnl_unlock();
 }
-#endif /* CONFIG_IEEE802154_NL802154_EXPERIMENTAL */
 
 /* message building helper */
 static inline void *nl802154hdr_put(struct sk_buff *skb, u32 portid, u32 seq,
@@ -1087,6 +1087,15 @@ static int nl802154_set_pan_id(struct sk_buff *skb, struct genl_info *info)
 
        pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
 
+       /* Only allow changing the PAN ID when the device has no more
+        * associations ongoing to avoid confusing peers.
+        */
+       if (cfg802154_device_is_associated(wpan_dev)) {
+               NL_SET_ERR_MSG(info->extack,
+                              "Existing associations, changing PAN ID forbidden");
+               return -EINVAL;
+       }
+
        return rdev_set_pan_id(rdev, wpan_dev, pan_id);
 }
 
@@ -1113,20 +1122,17 @@ static int nl802154_set_short_addr(struct sk_buff *skb, struct genl_info *info)
 
        short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
 
-       /* TODO
-        * I am not sure about to check here on broadcast short_addr.
-        * Broadcast is a valid setting, comment from 802.15.4:
-        * A value of 0xfffe indicates that the device has
-        * associated but has not been allocated an address. A
-        * value of 0xffff indicates that the device does not
-        * have a short address.
-        *
-        * I think we should allow to set these settings but
-        * don't allow to allow socket communication with it.
+       /* The short address only has a meaning when part of a PAN, after a
+        * proper association procedure. However, we want to still offer the
+        * possibility to create static networks so changing the short address
+        * is only allowed when not already associated to other devices with
+        * the official handshake.
         */
-       if (short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_UNSPEC) ||
-           short_addr == cpu_to_le16(IEEE802154_ADDR_SHORT_BROADCAST))
+       if (cfg802154_device_is_associated(wpan_dev)) {
+               NL_SET_ERR_MSG(info->extack,
+                              "Existing associations, changing short address forbidden");
                return -EINVAL;
+       }
 
        return rdev_set_short_addr(rdev, wpan_dev, short_addr);
 }
@@ -1628,6 +1634,189 @@ nl802154_stop_beacons(struct sk_buff *skb, struct genl_info *info)
        return rdev_stop_beacons(rdev, wpan_dev);
 }
 
+static int nl802154_associate(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg802154_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wpan_dev *wpan_dev;
+       struct wpan_phy *wpan_phy;
+       struct ieee802154_addr coord;
+       int err;
+
+       wpan_dev = dev->ieee802154_ptr;
+       wpan_phy = &rdev->wpan_phy;
+
+       if (wpan_phy->flags & WPAN_PHY_FLAG_DATAGRAMS_ONLY) {
+               NL_SET_ERR_MSG(info->extack, "PHY only supports datagrams");
+               return -EOPNOTSUPP;
+       }
+
+       if (!info->attrs[NL802154_ATTR_PAN_ID] ||
+           !info->attrs[NL802154_ATTR_EXTENDED_ADDR])
+               return -EINVAL;
+
+       coord.pan_id = nla_get_le16(info->attrs[NL802154_ATTR_PAN_ID]);
+       coord.mode = IEEE802154_ADDR_LONG;
+       coord.extended_addr = nla_get_le64(info->attrs[NL802154_ATTR_EXTENDED_ADDR]);
+
+       mutex_lock(&wpan_dev->association_lock);
+       err = rdev_associate(rdev, wpan_dev, &coord);
+       mutex_unlock(&wpan_dev->association_lock);
+       if (err)
+               pr_err("Association with PAN ID 0x%x failed (%d)\n",
+                      le16_to_cpu(coord.pan_id), err);
+
+       return err;
+}
+
+static int nl802154_disassociate(struct sk_buff *skb, struct genl_info *info)
+{
+       struct cfg802154_registered_device *rdev = info->user_ptr[0];
+       struct net_device *dev = info->user_ptr[1];
+       struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+       struct wpan_phy *wpan_phy = &rdev->wpan_phy;
+       struct ieee802154_addr target;
+
+       if (wpan_phy->flags & WPAN_PHY_FLAG_DATAGRAMS_ONLY) {
+               NL_SET_ERR_MSG(info->extack, "PHY only supports datagrams");
+               return -EOPNOTSUPP;
+       }
+
+       target.pan_id = wpan_dev->pan_id;
+
+       if (info->attrs[NL802154_ATTR_EXTENDED_ADDR]) {
+               target.mode = IEEE802154_ADDR_LONG;
+               target.extended_addr = nla_get_le64(info->attrs[NL802154_ATTR_EXTENDED_ADDR]);
+       } else if (info->attrs[NL802154_ATTR_SHORT_ADDR]) {
+               target.mode = IEEE802154_ADDR_SHORT;
+               target.short_addr = nla_get_le16(info->attrs[NL802154_ATTR_SHORT_ADDR]);
+       } else {
+               NL_SET_ERR_MSG(info->extack, "Device address is missing");
+               return -EINVAL;
+       }
+
+       mutex_lock(&wpan_dev->association_lock);
+       rdev_disassociate(rdev, wpan_dev, &target);
+       mutex_unlock(&wpan_dev->association_lock);
+
+       return 0;
+}
+
+static int nl802154_set_max_associations(struct sk_buff *skb, struct genl_info *info)
+{
+       struct net_device *dev = info->user_ptr[1];
+       struct wpan_dev *wpan_dev = dev->ieee802154_ptr;
+       unsigned int max_assoc;
+
+       if (!info->attrs[NL802154_ATTR_MAX_ASSOCIATIONS]) {
+               NL_SET_ERR_MSG(info->extack, "No maximum number of association given");
+               return -EINVAL;
+       }
+
+       max_assoc = nla_get_u32(info->attrs[NL802154_ATTR_MAX_ASSOCIATIONS]);
+
+       mutex_lock(&wpan_dev->association_lock);
+       cfg802154_set_max_associations(wpan_dev, max_assoc);
+       mutex_unlock(&wpan_dev->association_lock);
+
+       return 0;
+}
+
+static int nl802154_send_peer_info(struct sk_buff *msg,
+                                  struct netlink_callback *cb,
+                                  u32 seq, int flags,
+                                  struct cfg802154_registered_device *rdev,
+                                  struct wpan_dev *wpan_dev,
+                                  struct ieee802154_pan_device *peer,
+                                  enum nl802154_peer_type type)
+{
+       struct nlattr *nla;
+       void *hdr;
+
+       ASSERT_RTNL();
+
+       hdr = nl802154hdr_put(msg, NETLINK_CB(cb->skb).portid, seq, flags,
+                             NL802154_CMD_LIST_ASSOCIATIONS);
+       if (!hdr)
+               return -ENOBUFS;
+
+       genl_dump_check_consistent(cb, hdr);
+
+       nla = nla_nest_start_noflag(msg, NL802154_ATTR_PEER);
+       if (!nla)
+               goto nla_put_failure;
+
+       if (nla_put_u8(msg, NL802154_DEV_ADDR_ATTR_PEER_TYPE, type))
+               goto nla_put_failure;
+
+       if (nla_put_u8(msg, NL802154_DEV_ADDR_ATTR_MODE, peer->mode))
+               goto nla_put_failure;
+
+       if (nla_put(msg, NL802154_DEV_ADDR_ATTR_SHORT,
+                   IEEE802154_SHORT_ADDR_LEN, &peer->short_addr))
+               goto nla_put_failure;
+
+       if (nla_put(msg, NL802154_DEV_ADDR_ATTR_EXTENDED,
+                   IEEE802154_EXTENDED_ADDR_LEN, &peer->extended_addr))
+               goto nla_put_failure;
+
+       nla_nest_end(msg, nla);
+
+       genlmsg_end(msg, hdr);
+
+       return 0;
+
+ nla_put_failure:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int nl802154_list_associations(struct sk_buff *skb,
+                                     struct netlink_callback *cb)
+{
+       struct cfg802154_registered_device *rdev;
+       struct ieee802154_pan_device *child;
+       struct wpan_dev *wpan_dev;
+       int err;
+
+       err = nl802154_prepare_wpan_dev_dump(skb, cb, &rdev, &wpan_dev);
+       if (err)
+               return err;
+
+       mutex_lock(&wpan_dev->association_lock);
+
+       if (cb->args[2])
+               goto out;
+
+       if (wpan_dev->parent) {
+               err = nl802154_send_peer_info(skb, cb, cb->nlh->nlmsg_seq,
+                                             NLM_F_MULTI, rdev, wpan_dev,
+                                             wpan_dev->parent,
+                                             NL802154_PEER_TYPE_PARENT);
+               if (err < 0)
+                       goto out_err;
+       }
+
+       list_for_each_entry(child, &wpan_dev->children, node) {
+               err = nl802154_send_peer_info(skb, cb, cb->nlh->nlmsg_seq,
+                                             NLM_F_MULTI, rdev, wpan_dev,
+                                             child,
+                                             NL802154_PEER_TYPE_CHILD);
+               if (err < 0)
+                       goto out_err;
+       }
+
+       cb->args[2] = 1;
+out:
+       err = skb->len;
+out_err:
+       mutex_unlock(&wpan_dev->association_lock);
+
+       nl802154_finish_wpan_dev_dump(rdev);
+
+       return err;
+}
+
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
 static const struct nla_policy nl802154_dev_addr_policy[NL802154_DEV_ADDR_ATTR_MAX + 1] = {
        [NL802154_DEV_ADDR_ATTR_PAN_ID] = { .type = NLA_U16 },
@@ -2749,6 +2938,34 @@ static const struct genl_ops nl802154_ops[] = {
                                  NL802154_FLAG_CHECK_NETDEV_UP |
                                  NL802154_FLAG_NEED_RTNL,
        },
+       {
+               .cmd = NL802154_CMD_ASSOCIATE,
+               .doit = nl802154_associate,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL802154_FLAG_NEED_NETDEV |
+                                 NL802154_FLAG_CHECK_NETDEV_UP |
+                                 NL802154_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL802154_CMD_DISASSOCIATE,
+               .doit = nl802154_disassociate,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL802154_FLAG_NEED_NETDEV |
+                                 NL802154_FLAG_CHECK_NETDEV_UP |
+                                 NL802154_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL802154_CMD_SET_MAX_ASSOCIATIONS,
+               .doit = nl802154_set_max_associations,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = NL802154_FLAG_NEED_NETDEV |
+                                 NL802154_FLAG_NEED_RTNL,
+       },
+       {
+               .cmd = NL802154_CMD_LIST_ASSOCIATIONS,
+               .dumpit = nl802154_list_associations,
+               /* can be retrieved by unprivileged users */
+       },
 #ifdef CONFIG_IEEE802154_NL802154_EXPERIMENTAL
        {
                .cmd = NL802154_CMD_SET_SEC_PARAMS,