Merge branch 'rtnetlink-refactor-rtnl_-new-del-set-link-for-per-netns-rtnl'

Kuniyuki Iwashima says:

====================
rtnetlink: Refactor rtnl_{new,del,set}link() for per-netns RTNL.

This is a prep for the next series where we will push RTNL down to
rtnl_{new,del,set}link().

That means, for example, __rtnl_newlink() is always under RTNL, but
rtnl_newlink() has a non-RTNL section.

As a prerequisite for per-netns RTNL, we will move netns validation
(and RTNL-independent validations if possible) to that section.

rtnl_link_ops and rtnl_af_ops will be protected with SRCU not to
depend on RTNL.

Changes:
  v2:
    * Add Eric's Reviewed-by to patch 1-4,6,8-11, (no tag on 5,7,12-14)
    * Patch 7
      * Handle error of init_srcu_struct().
      * Call cleanup_srcu_struct() after synchronize_srcu().
    * Patch 12
      * Move put_net() before errorout label
    * Patch 13
      * Newly added as prep for patch 14
    * Patch 14
      * Handle error of init_srcu_struct().
      * Call cleanup_srcu_struct() after synchronize_srcu().

  v1: https://lore.kernel.org/netdev/20241009231656.57830-1-kuniyu@amazon.com/
====================

Link: https://patch.msgid.link/20241016185357.83849-1-kuniyu@amazon.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Paolo Abeni
2024-10-22 11:02:12 +02:00
7 changed files with 355 additions and 267 deletions

View File

@@ -3,6 +3,7 @@
#define __NET_RTNETLINK_H
#include <linux/rtnetlink.h>
#include <linux/srcu.h>
#include <net/netlink.h>
typedef int (*rtnl_doit_func)(struct sk_buff *, struct nlmsghdr *,
@@ -69,7 +70,8 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
/**
* struct rtnl_link_ops - rtnetlink link operations
*
* @list: Used internally
* @list: Used internally, protected by RTNL and SRCU
* @srcu: Used internally
* @kind: Identifier
* @netns_refund: Physical device, move to init_net on netns exit
* @maxtype: Highest device specific netlink attribute number
@@ -100,6 +102,7 @@ static inline int rtnl_msg_family(const struct nlmsghdr *nlh)
*/
struct rtnl_link_ops {
struct list_head list;
struct srcu_struct srcu;
const char *kind;
@@ -169,7 +172,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
/**
* struct rtnl_af_ops - rtnetlink address family operations
*
* @list: Used internally
* @list: Used internally, protected by RTNL and SRCU
* @srcu: Used internally
* @family: Address family
* @fill_link_af: Function to fill IFLA_AF_SPEC with address family
* specific netlink attributes.
@@ -182,6 +186,8 @@ void rtnl_link_unregister(struct rtnl_link_ops *ops);
*/
struct rtnl_af_ops {
struct list_head list;
struct srcu_struct srcu;
int family;
int (*fill_link_af)(struct sk_buff *skb,
@@ -201,7 +207,7 @@ struct rtnl_af_ops {
size_t (*get_stats_af_size)(const struct net_device *dev);
};
void rtnl_af_register(struct rtnl_af_ops *ops);
int rtnl_af_register(struct rtnl_af_ops *ops);
void rtnl_af_unregister(struct rtnl_af_ops *ops);
struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[]);

View File

@@ -1924,7 +1924,9 @@ int __init br_netlink_init(void)
if (err)
goto out;
rtnl_af_register(&br_af_ops);
err = rtnl_af_register(&br_af_ops);
if (err)
goto out_vlan;
err = rtnl_link_register(&br_link_ops);
if (err)
@@ -1934,6 +1936,8 @@ int __init br_netlink_init(void)
out_af:
rtnl_af_unregister(&br_af_ops);
out_vlan:
br_vlan_rtnl_uninit();
out:
return err;
}

View File

@@ -457,15 +457,29 @@ EXPORT_SYMBOL_GPL(__rtnl_unregister_many);
static LIST_HEAD(link_ops);
static const struct rtnl_link_ops *rtnl_link_ops_get(const char *kind)
static struct rtnl_link_ops *rtnl_link_ops_get(const char *kind, int *srcu_index)
{
const struct rtnl_link_ops *ops;
struct rtnl_link_ops *ops;
list_for_each_entry(ops, &link_ops, list) {
if (!strcmp(ops->kind, kind))
return ops;
rcu_read_lock();
list_for_each_entry_rcu(ops, &link_ops, list) {
if (!strcmp(ops->kind, kind)) {
*srcu_index = srcu_read_lock(&ops->srcu);
goto unlock;
}
}
return NULL;
ops = NULL;
unlock:
rcu_read_unlock();
return ops;
}
static void rtnl_link_ops_put(struct rtnl_link_ops *ops, int srcu_index)
{
srcu_read_unlock(&ops->srcu, srcu_index);
}
/**
@@ -480,8 +494,16 @@ static const struct rtnl_link_ops *rtnl_link_ops_get(const char *kind)
*/
int __rtnl_link_register(struct rtnl_link_ops *ops)
{
if (rtnl_link_ops_get(ops->kind))
return -EEXIST;
struct rtnl_link_ops *tmp;
int err;
/* When RTNL is removed, add lock for link_ops. */
ASSERT_RTNL();
list_for_each_entry(tmp, &link_ops, list) {
if (!strcmp(ops->kind, tmp->kind))
return -EEXIST;
}
/* The check for alloc/setup is here because if ops
* does not have that filled up, it is not possible
@@ -491,7 +513,12 @@ int __rtnl_link_register(struct rtnl_link_ops *ops)
if ((ops->alloc || ops->setup) && !ops->dellink)
ops->dellink = unregister_netdevice_queue;
list_add_tail(&ops->list, &link_ops);
err = init_srcu_struct(&ops->srcu);
if (err)
return err;
list_add_tail_rcu(&ops->list, &link_ops);
return 0;
}
EXPORT_SYMBOL_GPL(__rtnl_link_register);
@@ -542,10 +569,12 @@ void __rtnl_link_unregister(struct rtnl_link_ops *ops)
{
struct net *net;
for_each_net(net) {
list_del_rcu(&ops->list);
synchronize_srcu(&ops->srcu);
cleanup_srcu_struct(&ops->srcu);
for_each_net(net)
__rtnl_kill_links(net, ops);
}
list_del(&ops->list);
}
EXPORT_SYMBOL_GPL(__rtnl_link_unregister);
@@ -637,18 +666,31 @@ static size_t rtnl_link_get_size(const struct net_device *dev)
static LIST_HEAD(rtnl_af_ops);
static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
static struct rtnl_af_ops *rtnl_af_lookup(const int family, int *srcu_index)
{
const struct rtnl_af_ops *ops;
struct rtnl_af_ops *ops;
ASSERT_RTNL();
list_for_each_entry(ops, &rtnl_af_ops, list) {
if (ops->family == family)
return ops;
rcu_read_lock();
list_for_each_entry_rcu(ops, &rtnl_af_ops, list) {
if (ops->family == family) {
*srcu_index = srcu_read_lock(&ops->srcu);
goto unlock;
}
}
return NULL;
ops = NULL;
unlock:
rcu_read_unlock();
return ops;
}
static void rtnl_af_put(struct rtnl_af_ops *ops, int srcu_index)
{
srcu_read_unlock(&ops->srcu, srcu_index);
}
/**
@@ -657,11 +699,18 @@ static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
*
* Returns 0 on success or a negative error code.
*/
void rtnl_af_register(struct rtnl_af_ops *ops)
int rtnl_af_register(struct rtnl_af_ops *ops)
{
int err = init_srcu_struct(&ops->srcu);
if (err)
return err;
rtnl_lock();
list_add_tail_rcu(&ops->list, &rtnl_af_ops);
rtnl_unlock();
return 0;
}
EXPORT_SYMBOL_GPL(rtnl_af_register);
@@ -676,6 +725,8 @@ void rtnl_af_unregister(struct rtnl_af_ops *ops)
rtnl_unlock();
synchronize_rcu();
synchronize_srcu(&ops->srcu);
cleanup_srcu_struct(&ops->srcu);
}
EXPORT_SYMBOL_GPL(rtnl_af_unregister);
@@ -2158,10 +2209,11 @@ static const struct nla_policy ifla_xdp_policy[IFLA_XDP_MAX + 1] = {
[IFLA_XDP_PROG_ID] = { .type = NLA_U32 },
};
static const struct rtnl_link_ops *linkinfo_to_kind_ops(const struct nlattr *nla)
static struct rtnl_link_ops *linkinfo_to_kind_ops(const struct nlattr *nla,
int *ops_srcu_index)
{
const struct rtnl_link_ops *ops = NULL;
struct nlattr *linfo[IFLA_INFO_MAX + 1];
struct rtnl_link_ops *ops = NULL;
if (nla_parse_nested_deprecated(linfo, IFLA_INFO_MAX, nla, ifla_info_policy, NULL) < 0)
return NULL;
@@ -2170,7 +2222,7 @@ static const struct rtnl_link_ops *linkinfo_to_kind_ops(const struct nlattr *nla
char kind[MODULE_NAME_LEN];
nla_strscpy(kind, linfo[IFLA_INFO_KIND], sizeof(kind));
ops = rtnl_link_ops_get(kind);
ops = rtnl_link_ops_get(kind, ops_srcu_index);
}
return ops;
@@ -2290,8 +2342,8 @@ static int rtnl_valid_dump_ifinfo_req(const struct nlmsghdr *nlh,
static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
{
const struct rtnl_link_ops *kind_ops = NULL;
struct netlink_ext_ack *extack = cb->extack;
struct rtnl_link_ops *kind_ops = NULL;
const struct nlmsghdr *nlh = cb->nlh;
struct net *net = sock_net(skb->sk);
unsigned int flags = NLM_F_MULTI;
@@ -2302,6 +2354,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
struct net *tgt_net = net;
u32 ext_filter_mask = 0;
struct net_device *dev;
int ops_srcu_index;
int master_idx = 0;
int netnsid = -1;
int err, i;
@@ -2335,7 +2388,7 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
master_idx = nla_get_u32(tb[i]);
break;
case IFLA_LINKINFO:
kind_ops = linkinfo_to_kind_ops(tb[i]);
kind_ops = linkinfo_to_kind_ops(tb[i], &ops_srcu_index);
break;
default:
if (cb->strict_check) {
@@ -2361,6 +2414,10 @@ static int rtnl_dump_ifinfo(struct sk_buff *skb, struct netlink_callback *cb)
if (err < 0)
break;
}
if (kind_ops)
rtnl_link_ops_put(kind_ops, ops_srcu_index);
cb->seq = tgt_net->dev_base_seq;
nl_dump_check_consistent(cb, nlmsg_hdr(skb));
if (netnsid >= 0)
@@ -2542,20 +2599,24 @@ static int validate_linkmsg(struct net_device *dev, struct nlattr *tb[],
int rem, err;
nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
const struct rtnl_af_ops *af_ops;
struct rtnl_af_ops *af_ops;
int af_ops_srcu_index;
af_ops = rtnl_af_lookup(nla_type(af));
af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
if (!af_ops)
return -EAFNOSUPPORT;
if (!af_ops->set_link_af)
return -EOPNOTSUPP;
if (af_ops->validate_link_af) {
err = -EOPNOTSUPP;
else if (af_ops->validate_link_af)
err = af_ops->validate_link_af(dev, af, extack);
if (err < 0)
return err;
}
else
err = 0;
rtnl_af_put(af_ops, af_ops_srcu_index);
if (err < 0)
return err;
}
}
@@ -2846,8 +2907,8 @@ static int do_set_proto_down(struct net_device *dev,
#define DO_SETLINK_MODIFIED 0x01
/* notify flag means notify + modified. */
#define DO_SETLINK_NOTIFY 0x03
static int do_setlink(const struct sk_buff *skb,
struct net_device *dev, struct ifinfomsg *ifm,
static int do_setlink(const struct sk_buff *skb, struct net_device *dev,
struct net *tgt_net, struct ifinfomsg *ifm,
struct netlink_ext_ack *extack,
struct nlattr **tb, int status)
{
@@ -2855,32 +2916,28 @@ static int do_setlink(const struct sk_buff *skb,
char ifname[IFNAMSIZ];
int err;
err = validate_linkmsg(dev, tb, extack);
if (err < 0)
goto errout;
if (tb[IFLA_IFNAME])
nla_strscpy(ifname, tb[IFLA_IFNAME], IFNAMSIZ);
else
ifname[0] = '\0';
if (tb[IFLA_NET_NS_PID] || tb[IFLA_NET_NS_FD] || tb[IFLA_TARGET_NETNSID]) {
if (!net_eq(tgt_net, dev_net(dev))) {
const char *pat = ifname[0] ? ifname : NULL;
struct net *net;
int new_ifindex;
net = rtnl_link_get_net_capable(skb, dev_net(dev),
tb, CAP_NET_ADMIN);
if (IS_ERR(net)) {
err = PTR_ERR(net);
goto errout;
}
if (tb[IFLA_NEW_IFINDEX])
new_ifindex = nla_get_s32(tb[IFLA_NEW_IFINDEX]);
else
new_ifindex = 0;
err = __dev_change_net_namespace(dev, net, pat, new_ifindex);
put_net(net);
err = __dev_change_net_namespace(dev, tgt_net, pat, new_ifindex);
if (err)
goto errout;
status |= DO_SETLINK_MODIFIED;
}
@@ -3139,11 +3196,18 @@ static int do_setlink(const struct sk_buff *skb,
int rem;
nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
const struct rtnl_af_ops *af_ops;
struct rtnl_af_ops *af_ops;
int af_ops_srcu_index;
BUG_ON(!(af_ops = rtnl_af_lookup(nla_type(af))));
af_ops = rtnl_af_lookup(nla_type(af), &af_ops_srcu_index);
if (!af_ops) {
err = -EAFNOSUPPORT;
goto errout;
}
err = af_ops->set_link_af(dev, af, extack);
rtnl_af_put(af_ops, af_ops_srcu_index);
if (err < 0)
goto errout;
@@ -3240,11 +3304,12 @@ static struct net_device *rtnl_dev_get(struct net *net,
static int rtnl_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct ifinfomsg *ifm = nlmsg_data(nlh);
struct net *net = sock_net(skb->sk);
struct ifinfomsg *ifm;
struct net_device *dev;
int err;
struct nlattr *tb[IFLA_MAX+1];
struct net_device *dev = NULL;
struct net *tgt_net;
int err;
err = nlmsg_parse_deprecated(nlh, sizeof(*ifm), tb, IFLA_MAX,
ifla_policy, extack);
@@ -3255,25 +3320,25 @@ static int rtnl_setlink(struct sk_buff *skb, struct nlmsghdr *nlh,
if (err < 0)
goto errout;
err = -EINVAL;
ifm = nlmsg_data(nlh);
tgt_net = rtnl_link_get_net_capable(skb, net, tb, CAP_NET_ADMIN);
if (IS_ERR(tgt_net)) {
err = PTR_ERR(tgt_net);
goto errout;
}
if (ifm->ifi_index > 0)
dev = __dev_get_by_index(net, ifm->ifi_index);
else if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME])
dev = rtnl_dev_get(net, tb);
else
goto errout;
err = -EINVAL;
if (dev == NULL) {
if (dev)
err = do_setlink(skb, dev, tgt_net, ifm, extack, tb, 0);
else if (!err)
err = -ENODEV;
goto errout;
}
err = validate_linkmsg(dev, tb, extack);
if (err < 0)
goto errout;
err = do_setlink(skb, dev, ifm, extack, tb, 0);
put_net(tgt_net);
errout:
return err;
}
@@ -3333,14 +3398,14 @@ EXPORT_SYMBOL_GPL(rtnl_delete_link);
static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct ifinfomsg *ifm = nlmsg_data(nlh);
struct net *net = sock_net(skb->sk);
u32 portid = NETLINK_CB(skb).portid;
struct net *tgt_net = net;
struct net_device *dev = NULL;
struct ifinfomsg *ifm;
struct nlattr *tb[IFLA_MAX+1];
int err;
struct net_device *dev = NULL;
struct net *tgt_net = net;
int netnsid = -1;
int err;
err = nlmsg_parse_deprecated(nlh, sizeof(*ifm), tb, IFLA_MAX,
ifla_policy, extack);
@@ -3358,27 +3423,20 @@ static int rtnl_dellink(struct sk_buff *skb, struct nlmsghdr *nlh,
return PTR_ERR(tgt_net);
}
err = -EINVAL;
ifm = nlmsg_data(nlh);
if (ifm->ifi_index > 0)
dev = __dev_get_by_index(tgt_net, ifm->ifi_index);
else if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME])
dev = rtnl_dev_get(tgt_net, tb);
if (dev)
err = rtnl_delete_link(dev, portid, nlh);
else if (ifm->ifi_index > 0 || tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME])
err = -ENODEV;
else if (tb[IFLA_GROUP])
err = rtnl_group_dellink(tgt_net, nla_get_u32(tb[IFLA_GROUP]));
else
goto out;
err = -EINVAL;
if (!dev) {
if (tb[IFLA_IFNAME] || tb[IFLA_ALT_IFNAME] || ifm->ifi_index > 0)
err = -ENODEV;
goto out;
}
err = rtnl_delete_link(dev, portid, nlh);
out:
if (netnsid >= 0)
put_net(tgt_net);
@@ -3505,21 +3563,90 @@ struct net_device *rtnl_create_link(struct net *net, const char *ifname,
}
EXPORT_SYMBOL(rtnl_create_link);
struct rtnl_newlink_tbs {
struct nlattr *tb[IFLA_MAX + 1];
struct nlattr *linkinfo[IFLA_INFO_MAX + 1];
struct nlattr *attr[RTNL_MAX_TYPE + 1];
struct nlattr *slave_attr[RTNL_SLAVE_MAX_TYPE + 1];
};
static int rtnl_changelink(const struct sk_buff *skb, struct nlmsghdr *nlh,
const struct rtnl_link_ops *ops,
struct net_device *dev, struct net *tgt_net,
struct rtnl_newlink_tbs *tbs,
struct nlattr **data,
struct netlink_ext_ack *extack)
{
struct nlattr ** const linkinfo = tbs->linkinfo;
struct nlattr ** const tb = tbs->tb;
int status = 0;
int err;
if (nlh->nlmsg_flags & NLM_F_EXCL)
return -EEXIST;
if (nlh->nlmsg_flags & NLM_F_REPLACE)
return -EOPNOTSUPP;
if (linkinfo[IFLA_INFO_DATA]) {
if (!ops || ops != dev->rtnl_link_ops || !ops->changelink)
return -EOPNOTSUPP;
err = ops->changelink(dev, tb, data, extack);
if (err < 0)
return err;
status |= DO_SETLINK_NOTIFY;
}
if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
const struct rtnl_link_ops *m_ops = NULL;
struct nlattr **slave_data = NULL;
struct net_device *master_dev;
master_dev = netdev_master_upper_dev_get(dev);
if (master_dev)
m_ops = master_dev->rtnl_link_ops;
if (!m_ops || !m_ops->slave_changelink)
return -EOPNOTSUPP;
if (m_ops->slave_maxtype > RTNL_SLAVE_MAX_TYPE)
return -EINVAL;
if (m_ops->slave_maxtype) {
err = nla_parse_nested_deprecated(tbs->slave_attr,
m_ops->slave_maxtype,
linkinfo[IFLA_INFO_SLAVE_DATA],
m_ops->slave_policy, extack);
if (err < 0)
return err;
slave_data = tbs->slave_attr;
}
err = m_ops->slave_changelink(master_dev, dev, tb, slave_data, extack);
if (err < 0)
return err;
status |= DO_SETLINK_NOTIFY;
}
return do_setlink(skb, dev, tgt_net, nlmsg_data(nlh), extack, tb, status);
}
static int rtnl_group_changelink(const struct sk_buff *skb,
struct net *net, int group,
struct ifinfomsg *ifm,
struct netlink_ext_ack *extack,
struct nlattr **tb)
struct net *net, struct net *tgt_net,
int group, struct ifinfomsg *ifm,
struct netlink_ext_ack *extack,
struct nlattr **tb)
{
struct net_device *dev, *aux;
int err;
for_each_netdev_safe(net, dev, aux) {
if (dev->group == group) {
err = validate_linkmsg(dev, tb, extack);
if (err < 0)
return err;
err = do_setlink(skb, dev, ifm, extack, tb, 0);
err = do_setlink(skb, dev, tgt_net, ifm, extack, tb, 0);
if (err < 0)
return err;
}
@@ -3530,6 +3657,7 @@ static int rtnl_group_changelink(const struct sk_buff *skb,
static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
const struct rtnl_link_ops *ops,
struct net *tgt_net, struct net *link_net,
const struct nlmsghdr *nlh,
struct nlattr **tb, struct nlattr **data,
struct netlink_ext_ack *extack)
@@ -3537,7 +3665,6 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
unsigned char name_assign_type = NET_NAME_USER;
struct net *net = sock_net(skb->sk);
u32 portid = NETLINK_CB(skb).portid;
struct net *dest_net, *link_net;
struct net_device *dev;
char ifname[IFNAMSIZ];
int err;
@@ -3552,27 +3679,7 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
name_assign_type = NET_NAME_ENUM;
}
dest_net = rtnl_link_get_net_capable(skb, net, tb, CAP_NET_ADMIN);
if (IS_ERR(dest_net))
return PTR_ERR(dest_net);
if (tb[IFLA_LINK_NETNSID]) {
int id = nla_get_s32(tb[IFLA_LINK_NETNSID]);
link_net = get_net_ns_by_id(dest_net, id);
if (!link_net) {
NL_SET_ERR_MSG(extack, "Unknown network namespace id");
err = -EINVAL;
goto out;
}
err = -EPERM;
if (!netlink_ns_capable(skb, link_net->user_ns, CAP_NET_ADMIN))
goto out;
} else {
link_net = NULL;
}
dev = rtnl_create_link(link_net ? : dest_net, ifname,
dev = rtnl_create_link(link_net ? : tgt_net, ifname,
name_assign_type, ops, tb, extack);
if (IS_ERR(dev)) {
err = PTR_ERR(dev);
@@ -3594,7 +3701,7 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
if (err < 0)
goto out_unregister;
if (link_net) {
err = dev_change_net_namespace(dev, dest_net, ifname);
err = dev_change_net_namespace(dev, tgt_net, ifname);
if (err < 0)
goto out_unregister;
}
@@ -3604,9 +3711,6 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
goto out_unregister;
}
out:
if (link_net)
put_net(link_net);
put_net(dest_net);
return err;
out_unregister:
if (ops->newlink) {
@@ -3620,41 +3724,18 @@ static int rtnl_newlink_create(struct sk_buff *skb, struct ifinfomsg *ifm,
goto out;
}
struct rtnl_newlink_tbs {
struct nlattr *tb[IFLA_MAX + 1];
struct nlattr *attr[RTNL_MAX_TYPE + 1];
struct nlattr *slave_attr[RTNL_SLAVE_MAX_TYPE + 1];
};
static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
const struct rtnl_link_ops *ops,
struct net *tgt_net, struct net *link_net,
struct rtnl_newlink_tbs *tbs,
struct nlattr **data,
struct netlink_ext_ack *extack)
{
struct nlattr *linkinfo[IFLA_INFO_MAX + 1];
struct nlattr ** const tb = tbs->tb;
const struct rtnl_link_ops *m_ops;
struct net_device *master_dev;
struct net *net = sock_net(skb->sk);
const struct rtnl_link_ops *ops;
struct nlattr **slave_data;
char kind[MODULE_NAME_LEN];
struct net_device *dev;
struct ifinfomsg *ifm;
struct nlattr **data;
bool link_specified;
int err;
#ifdef CONFIG_MODULES
replay:
#endif
err = nlmsg_parse_deprecated(nlh, sizeof(*ifm), tb, IFLA_MAX,
ifla_policy, extack);
if (err < 0)
return err;
err = rtnl_ensure_unique_netns(tb, extack, false);
if (err < 0)
return err;
ifm = nlmsg_data(nlh);
if (ifm->ifi_index > 0) {
@@ -3671,151 +3752,135 @@ static int __rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
dev = NULL;
}
master_dev = NULL;
m_ops = NULL;
if (dev) {
master_dev = netdev_master_upper_dev_get(dev);
if (master_dev)
m_ops = master_dev->rtnl_link_ops;
}
if (tb[IFLA_LINKINFO]) {
err = nla_parse_nested_deprecated(linkinfo, IFLA_INFO_MAX,
tb[IFLA_LINKINFO],
ifla_info_policy, NULL);
if (err < 0)
return err;
} else
memset(linkinfo, 0, sizeof(linkinfo));
if (linkinfo[IFLA_INFO_KIND]) {
nla_strscpy(kind, linkinfo[IFLA_INFO_KIND], sizeof(kind));
ops = rtnl_link_ops_get(kind);
} else {
kind[0] = '\0';
ops = NULL;
}
data = NULL;
if (ops) {
if (ops->maxtype > RTNL_MAX_TYPE)
return -EINVAL;
if (ops->maxtype && linkinfo[IFLA_INFO_DATA]) {
err = nla_parse_nested_deprecated(tbs->attr, ops->maxtype,
linkinfo[IFLA_INFO_DATA],
ops->policy, extack);
if (err < 0)
return err;
data = tbs->attr;
}
if (ops->validate) {
err = ops->validate(tb, data, extack);
if (err < 0)
return err;
}
}
slave_data = NULL;
if (m_ops) {
if (m_ops->slave_maxtype > RTNL_SLAVE_MAX_TYPE)
return -EINVAL;
if (m_ops->slave_maxtype &&
linkinfo[IFLA_INFO_SLAVE_DATA]) {
err = nla_parse_nested_deprecated(tbs->slave_attr,
m_ops->slave_maxtype,
linkinfo[IFLA_INFO_SLAVE_DATA],
m_ops->slave_policy,
extack);
if (err < 0)
return err;
slave_data = tbs->slave_attr;
}
}
if (dev) {
int status = 0;
if (nlh->nlmsg_flags & NLM_F_EXCL)
return -EEXIST;
if (nlh->nlmsg_flags & NLM_F_REPLACE)
return -EOPNOTSUPP;
err = validate_linkmsg(dev, tb, extack);
if (err < 0)
return err;
if (linkinfo[IFLA_INFO_DATA]) {
if (!ops || ops != dev->rtnl_link_ops ||
!ops->changelink)
return -EOPNOTSUPP;
err = ops->changelink(dev, tb, data, extack);
if (err < 0)
return err;
status |= DO_SETLINK_NOTIFY;
}
if (linkinfo[IFLA_INFO_SLAVE_DATA]) {
if (!m_ops || !m_ops->slave_changelink)
return -EOPNOTSUPP;
err = m_ops->slave_changelink(master_dev, dev, tb,
slave_data, extack);
if (err < 0)
return err;
status |= DO_SETLINK_NOTIFY;
}
return do_setlink(skb, dev, ifm, extack, tb, status);
}
if (dev)
return rtnl_changelink(skb, nlh, ops, dev, tgt_net, tbs, data, extack);
if (!(nlh->nlmsg_flags & NLM_F_CREATE)) {
/* No dev found and NLM_F_CREATE not set. Requested dev does not exist,
* or it's for a group
*/
if (link_specified)
if (link_specified || !tb[IFLA_GROUP])
return -ENODEV;
if (tb[IFLA_GROUP])
return rtnl_group_changelink(skb, net,
nla_get_u32(tb[IFLA_GROUP]),
ifm, extack, tb);
return -ENODEV;
return rtnl_group_changelink(skb, net, tgt_net,
nla_get_u32(tb[IFLA_GROUP]),
ifm, extack, tb);
}
if (tb[IFLA_MAP] || tb[IFLA_PROTINFO])
return -EOPNOTSUPP;
if (!ops) {
#ifdef CONFIG_MODULES
if (kind[0]) {
__rtnl_unlock();
request_module("rtnl-link-%s", kind);
rtnl_lock();
ops = rtnl_link_ops_get(kind);
if (ops)
goto replay;
}
#endif
NL_SET_ERR_MSG(extack, "Unknown device type");
return -EOPNOTSUPP;
}
return rtnl_newlink_create(skb, ifm, ops, nlh, tb, data, extack);
return rtnl_newlink_create(skb, ifm, ops, tgt_net, link_net, nlh, tb, data, extack);
}
static int rtnl_newlink(struct sk_buff *skb, struct nlmsghdr *nlh,
struct netlink_ext_ack *extack)
{
struct nlattr **tb, **linkinfo, **data = NULL;
struct net *tgt_net, *link_net = NULL;
struct rtnl_link_ops *ops = NULL;
struct rtnl_newlink_tbs *tbs;
int ops_srcu_index;
int ret;
tbs = kmalloc(sizeof(*tbs), GFP_KERNEL);
if (!tbs)
return -ENOMEM;
ret = __rtnl_newlink(skb, nlh, tbs, extack);
tb = tbs->tb;
ret = nlmsg_parse_deprecated(nlh, sizeof(struct ifinfomsg), tb,
IFLA_MAX, ifla_policy, extack);
if (ret < 0)
goto free;
ret = rtnl_ensure_unique_netns(tb, extack, false);
if (ret < 0)
goto free;
linkinfo = tbs->linkinfo;
if (tb[IFLA_LINKINFO]) {
ret = nla_parse_nested_deprecated(linkinfo, IFLA_INFO_MAX,
tb[IFLA_LINKINFO],
ifla_info_policy, NULL);
if (ret < 0)
goto free;
} else {
memset(linkinfo, 0, sizeof(tbs->linkinfo));
}
if (linkinfo[IFLA_INFO_KIND]) {
char kind[MODULE_NAME_LEN];
nla_strscpy(kind, linkinfo[IFLA_INFO_KIND], sizeof(kind));
ops = rtnl_link_ops_get(kind, &ops_srcu_index);
#ifdef CONFIG_MODULES
if (!ops) {
__rtnl_unlock();
request_module("rtnl-link-%s", kind);
rtnl_lock();
ops = rtnl_link_ops_get(kind, &ops_srcu_index);
}
#endif
}
if (ops) {
if (ops->maxtype > RTNL_MAX_TYPE)
return -EINVAL;
if (ops->maxtype && linkinfo[IFLA_INFO_DATA]) {
ret = nla_parse_nested_deprecated(tbs->attr, ops->maxtype,
linkinfo[IFLA_INFO_DATA],
ops->policy, extack);
if (ret < 0)
goto put_ops;
data = tbs->attr;
}
if (ops->validate) {
ret = ops->validate(tb, data, extack);
if (ret < 0)
goto put_ops;
}
}
tgt_net = rtnl_link_get_net_capable(skb, sock_net(skb->sk), tb, CAP_NET_ADMIN);
if (IS_ERR(tgt_net)) {
ret = PTR_ERR(tgt_net);
goto put_ops;
}
if (tb[IFLA_LINK_NETNSID]) {
int id = nla_get_s32(tb[IFLA_LINK_NETNSID]);
link_net = get_net_ns_by_id(tgt_net, id);
if (!link_net) {
NL_SET_ERR_MSG(extack, "Unknown network namespace id");
ret = -EINVAL;
goto put_net;
}
if (!netlink_ns_capable(skb, link_net->user_ns, CAP_NET_ADMIN)) {
ret = -EPERM;
goto put_net;
}
}
ret = __rtnl_newlink(skb, nlh, ops, tgt_net, link_net, tbs, data, extack);
put_net:
if (link_net)
put_net(link_net);
put_net(tgt_net);
put_ops:
if (ops)
rtnl_link_ops_put(ops, ops_srcu_index);
free:
kfree(tbs);
return ret;
}

View File

@@ -2827,7 +2827,8 @@ void __init devinet_init(void)
register_pernet_subsys(&devinet_ops);
register_netdevice_notifier(&ip_netdev_notifier);
rtnl_af_register(&inet_af_ops);
if (rtnl_af_register(&inet_af_ops))
panic("Unable to register inet_af_ops\n");
rtnl_register_many(devinet_rtnl_msg_handlers);
}

View File

@@ -7468,7 +7468,9 @@ int __init addrconf_init(void)
addrconf_verify(&init_net);
rtnl_af_register(&inet6_ops);
err = rtnl_af_register(&inet6_ops);
if (err)
goto erraf;
err = rtnl_register_many(addrconf_rtnl_msg_handlers);
if (err)
@@ -7482,6 +7484,7 @@ int __init addrconf_init(void)
errout:
rtnl_unregister_all(PF_INET6);
rtnl_af_unregister(&inet6_ops);
erraf:
unregister_netdevice_notifier(&ipv6_dev_notf);
errlo:
destroy_workqueue(addrconf_wq);

View File

@@ -535,14 +535,20 @@ int __init mctp_device_init(void)
int err;
register_netdevice_notifier(&mctp_dev_nb);
rtnl_af_register(&mctp_af_ops);
err = rtnl_af_register(&mctp_af_ops);
if (err)
goto err_notifier;
err = rtnl_register_many(mctp_device_rtnl_msg_handlers);
if (err) {
rtnl_af_unregister(&mctp_af_ops);
unregister_netdevice_notifier(&mctp_dev_nb);
}
if (err)
goto err_af;
return 0;
err_af:
rtnl_af_unregister(&mctp_af_ops);
err_notifier:
unregister_netdevice_notifier(&mctp_dev_nb);
return err;
}

View File

@@ -2753,7 +2753,9 @@ static int __init mpls_init(void)
dev_add_pack(&mpls_packet_type);
rtnl_af_register(&mpls_af_ops);
err = rtnl_af_register(&mpls_af_ops);
if (err)
goto out_unregister_dev_type;
err = rtnl_register_many(mpls_rtnl_msg_handlers);
if (err)
@@ -2773,6 +2775,7 @@ static int __init mpls_init(void)
rtnl_unregister_many(mpls_rtnl_msg_handlers);
out_unregister_rtnl_af:
rtnl_af_unregister(&mpls_af_ops);
out_unregister_dev_type:
dev_remove_pack(&mpls_packet_type);
out_unregister_pernet:
unregister_pernet_subsys(&mpls_net_ops);