mirror of https://gitee.com/openkylin/linux.git
rtnetlink: Link address family API
Each net_device contains address family specific data such as per device settings and statistics. We already expose this data via procfs/sysfs and partially netlink. The netlink method requires the requester to send one RTM_GETLINK request for each address family it wishes to receive data of and then merge this data itself. This patch implements a new API which combines all address family specific link data in a new netlink attribute IFLA_AF_SPEC. IFLA_AF_SPEC contains a sequence of nested attributes, one for each address family which in turn defines the structure of its own attribute. Example: [IFLA_AF_SPEC] = { [AF_INET] = { [IFLA_INET_CONF] = ..., }, [AF_INET6] = { [IFLA_INET6_FLAGS] = ..., [IFLA_INET6_CONF] = ..., } } The API also allows for address families to implement a function which parses the IFLA_AF_SPEC attribute sent by userspace to implement address family specific link options. Signed-off-by: Thomas Graf <tgraf@infradead.org> Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
parent
d67ef35fff
commit
f8ff182c71
|
@ -80,6 +80,24 @@ struct rtnl_link_ifmap {
|
||||||
__u8 port;
|
__u8 port;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
* IFLA_AF_SPEC
|
||||||
|
* Contains nested attributes for address family specific attributes.
|
||||||
|
* Each address family may create a attribute with the address family
|
||||||
|
* number as type and create its own attribute structure in it.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* [IFLA_AF_SPEC] = {
|
||||||
|
* [AF_INET] = {
|
||||||
|
* [IFLA_INET_CONF] = ...,
|
||||||
|
* },
|
||||||
|
* [AF_INET6] = {
|
||||||
|
* [IFLA_INET6_FLAGS] = ...,
|
||||||
|
* [IFLA_INET6_CONF] = ...,
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*/
|
||||||
|
|
||||||
enum {
|
enum {
|
||||||
IFLA_UNSPEC,
|
IFLA_UNSPEC,
|
||||||
IFLA_ADDRESS,
|
IFLA_ADDRESS,
|
||||||
|
@ -116,6 +134,7 @@ enum {
|
||||||
IFLA_STATS64,
|
IFLA_STATS64,
|
||||||
IFLA_VF_PORTS,
|
IFLA_VF_PORTS,
|
||||||
IFLA_PORT_SELF,
|
IFLA_PORT_SELF,
|
||||||
|
IFLA_AF_SPEC,
|
||||||
__IFLA_MAX
|
__IFLA_MAX
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -83,6 +83,37 @@ extern void __rtnl_link_unregister(struct rtnl_link_ops *ops);
|
||||||
extern int rtnl_link_register(struct rtnl_link_ops *ops);
|
extern int rtnl_link_register(struct rtnl_link_ops *ops);
|
||||||
extern void rtnl_link_unregister(struct rtnl_link_ops *ops);
|
extern void rtnl_link_unregister(struct rtnl_link_ops *ops);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* struct rtnl_af_ops - rtnetlink address family operations
|
||||||
|
*
|
||||||
|
* @list: Used internally
|
||||||
|
* @family: Address family
|
||||||
|
* @fill_link_af: Function to fill IFLA_AF_SPEC with address family
|
||||||
|
* specific netlink attributes.
|
||||||
|
* @get_link_af_size: Function to calculate size of address family specific
|
||||||
|
* netlink attributes exlusive the container attribute.
|
||||||
|
* @parse_link_af: Function to parse a IFLA_AF_SPEC attribute and modify
|
||||||
|
* net_device accordingly.
|
||||||
|
*/
|
||||||
|
struct rtnl_af_ops {
|
||||||
|
struct list_head list;
|
||||||
|
int family;
|
||||||
|
|
||||||
|
int (*fill_link_af)(struct sk_buff *skb,
|
||||||
|
const struct net_device *dev);
|
||||||
|
size_t (*get_link_af_size)(const struct net_device *dev);
|
||||||
|
|
||||||
|
int (*parse_link_af)(struct net_device *dev,
|
||||||
|
const struct nlattr *attr);
|
||||||
|
};
|
||||||
|
|
||||||
|
extern int __rtnl_af_register(struct rtnl_af_ops *ops);
|
||||||
|
extern void __rtnl_af_unregister(struct rtnl_af_ops *ops);
|
||||||
|
|
||||||
|
extern int rtnl_af_register(struct rtnl_af_ops *ops);
|
||||||
|
extern void rtnl_af_unregister(struct rtnl_af_ops *ops);
|
||||||
|
|
||||||
|
|
||||||
extern struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[]);
|
extern struct net *rtnl_link_get_net(struct net *src_net, struct nlattr *tb[]);
|
||||||
extern struct net_device *rtnl_create_link(struct net *src_net, struct net *net,
|
extern struct net_device *rtnl_create_link(struct net *src_net, struct net *net,
|
||||||
char *ifname, const struct rtnl_link_ops *ops, struct nlattr *tb[]);
|
char *ifname, const struct rtnl_link_ops *ops, struct nlattr *tb[]);
|
||||||
|
|
|
@ -362,6 +362,95 @@ static size_t rtnl_link_get_size(const struct net_device *dev)
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LIST_HEAD(rtnl_af_ops);
|
||||||
|
|
||||||
|
static const struct rtnl_af_ops *rtnl_af_lookup(const int family)
|
||||||
|
{
|
||||||
|
const struct rtnl_af_ops *ops;
|
||||||
|
|
||||||
|
list_for_each_entry(ops, &rtnl_af_ops, list) {
|
||||||
|
if (ops->family == family)
|
||||||
|
return ops;
|
||||||
|
}
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __rtnl_af_register - Register rtnl_af_ops with rtnetlink.
|
||||||
|
* @ops: struct rtnl_af_ops * to register
|
||||||
|
*
|
||||||
|
* The caller must hold the rtnl_mutex.
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative error code.
|
||||||
|
*/
|
||||||
|
int __rtnl_af_register(struct rtnl_af_ops *ops)
|
||||||
|
{
|
||||||
|
list_add_tail(&ops->list, &rtnl_af_ops);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__rtnl_af_register);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rtnl_af_register - Register rtnl_af_ops with rtnetlink.
|
||||||
|
* @ops: struct rtnl_af_ops * to register
|
||||||
|
*
|
||||||
|
* Returns 0 on success or a negative error code.
|
||||||
|
*/
|
||||||
|
int rtnl_af_register(struct rtnl_af_ops *ops)
|
||||||
|
{
|
||||||
|
int err;
|
||||||
|
|
||||||
|
rtnl_lock();
|
||||||
|
err = __rtnl_af_register(ops);
|
||||||
|
rtnl_unlock();
|
||||||
|
return err;
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rtnl_af_register);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* __rtnl_af_unregister - Unregister rtnl_af_ops from rtnetlink.
|
||||||
|
* @ops: struct rtnl_af_ops * to unregister
|
||||||
|
*
|
||||||
|
* The caller must hold the rtnl_mutex.
|
||||||
|
*/
|
||||||
|
void __rtnl_af_unregister(struct rtnl_af_ops *ops)
|
||||||
|
{
|
||||||
|
list_del(&ops->list);
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(__rtnl_af_unregister);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* rtnl_af_unregister - Unregister rtnl_af_ops from rtnetlink.
|
||||||
|
* @ops: struct rtnl_af_ops * to unregister
|
||||||
|
*/
|
||||||
|
void rtnl_af_unregister(struct rtnl_af_ops *ops)
|
||||||
|
{
|
||||||
|
rtnl_lock();
|
||||||
|
__rtnl_af_unregister(ops);
|
||||||
|
rtnl_unlock();
|
||||||
|
}
|
||||||
|
EXPORT_SYMBOL_GPL(rtnl_af_unregister);
|
||||||
|
|
||||||
|
static size_t rtnl_link_get_af_size(const struct net_device *dev)
|
||||||
|
{
|
||||||
|
struct rtnl_af_ops *af_ops;
|
||||||
|
size_t size;
|
||||||
|
|
||||||
|
/* IFLA_AF_SPEC */
|
||||||
|
size = nla_total_size(sizeof(struct nlattr));
|
||||||
|
|
||||||
|
list_for_each_entry(af_ops, &rtnl_af_ops, list) {
|
||||||
|
if (af_ops->get_link_af_size) {
|
||||||
|
/* AF_* + nested data */
|
||||||
|
size += nla_total_size(sizeof(struct nlattr)) +
|
||||||
|
af_ops->get_link_af_size(dev);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return size;
|
||||||
|
}
|
||||||
|
|
||||||
static int rtnl_link_fill(struct sk_buff *skb, const struct net_device *dev)
|
static int rtnl_link_fill(struct sk_buff *skb, const struct net_device *dev)
|
||||||
{
|
{
|
||||||
const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
|
const struct rtnl_link_ops *ops = dev->rtnl_link_ops;
|
||||||
|
@ -671,7 +760,8 @@ static noinline size_t if_nlmsg_size(const struct net_device *dev)
|
||||||
+ nla_total_size(4) /* IFLA_NUM_VF */
|
+ nla_total_size(4) /* IFLA_NUM_VF */
|
||||||
+ rtnl_vfinfo_size(dev) /* IFLA_VFINFO_LIST */
|
+ rtnl_vfinfo_size(dev) /* IFLA_VFINFO_LIST */
|
||||||
+ rtnl_port_size(dev) /* IFLA_VF_PORTS + IFLA_PORT_SELF */
|
+ rtnl_port_size(dev) /* IFLA_VF_PORTS + IFLA_PORT_SELF */
|
||||||
+ rtnl_link_get_size(dev); /* IFLA_LINKINFO */
|
+ rtnl_link_get_size(dev) /* IFLA_LINKINFO */
|
||||||
|
+ rtnl_link_get_af_size(dev); /* IFLA_AF_SPEC */
|
||||||
}
|
}
|
||||||
|
|
||||||
static int rtnl_vf_ports_fill(struct sk_buff *skb, struct net_device *dev)
|
static int rtnl_vf_ports_fill(struct sk_buff *skb, struct net_device *dev)
|
||||||
|
@ -757,7 +847,8 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
|
||||||
struct nlmsghdr *nlh;
|
struct nlmsghdr *nlh;
|
||||||
struct rtnl_link_stats64 temp;
|
struct rtnl_link_stats64 temp;
|
||||||
const struct rtnl_link_stats64 *stats;
|
const struct rtnl_link_stats64 *stats;
|
||||||
struct nlattr *attr;
|
struct nlattr *attr, *af_spec;
|
||||||
|
struct rtnl_af_ops *af_ops;
|
||||||
|
|
||||||
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags);
|
nlh = nlmsg_put(skb, pid, seq, type, sizeof(*ifm), flags);
|
||||||
if (nlh == NULL)
|
if (nlh == NULL)
|
||||||
|
@ -866,6 +957,36 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
|
||||||
goto nla_put_failure;
|
goto nla_put_failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!(af_spec = nla_nest_start(skb, IFLA_AF_SPEC)))
|
||||||
|
goto nla_put_failure;
|
||||||
|
|
||||||
|
list_for_each_entry(af_ops, &rtnl_af_ops, list) {
|
||||||
|
if (af_ops->fill_link_af) {
|
||||||
|
struct nlattr *af;
|
||||||
|
int err;
|
||||||
|
|
||||||
|
if (!(af = nla_nest_start(skb, af_ops->family)))
|
||||||
|
goto nla_put_failure;
|
||||||
|
|
||||||
|
err = af_ops->fill_link_af(skb, dev);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Caller may return ENODATA to indicate that there
|
||||||
|
* was no data to be dumped. This is not an error, it
|
||||||
|
* means we should trim the attribute header and
|
||||||
|
* continue.
|
||||||
|
*/
|
||||||
|
if (err == -ENODATA)
|
||||||
|
nla_nest_cancel(skb, af);
|
||||||
|
else if (err < 0)
|
||||||
|
goto nla_put_failure;
|
||||||
|
|
||||||
|
nla_nest_end(skb, af);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nla_nest_end(skb, af_spec);
|
||||||
|
|
||||||
return nlmsg_end(skb, nlh);
|
return nlmsg_end(skb, nlh);
|
||||||
|
|
||||||
nla_put_failure:
|
nla_put_failure:
|
||||||
|
@ -924,6 +1045,7 @@ const struct nla_policy ifla_policy[IFLA_MAX+1] = {
|
||||||
[IFLA_VFINFO_LIST] = {. type = NLA_NESTED },
|
[IFLA_VFINFO_LIST] = {. type = NLA_NESTED },
|
||||||
[IFLA_VF_PORTS] = { .type = NLA_NESTED },
|
[IFLA_VF_PORTS] = { .type = NLA_NESTED },
|
||||||
[IFLA_PORT_SELF] = { .type = NLA_NESTED },
|
[IFLA_PORT_SELF] = { .type = NLA_NESTED },
|
||||||
|
[IFLA_AF_SPEC] = { .type = NLA_NESTED },
|
||||||
};
|
};
|
||||||
EXPORT_SYMBOL(ifla_policy);
|
EXPORT_SYMBOL(ifla_policy);
|
||||||
|
|
||||||
|
@ -1225,6 +1347,27 @@ static int do_setlink(struct net_device *dev, struct ifinfomsg *ifm,
|
||||||
goto errout;
|
goto errout;
|
||||||
modified = 1;
|
modified = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (tb[IFLA_AF_SPEC]) {
|
||||||
|
struct nlattr *af;
|
||||||
|
int rem;
|
||||||
|
|
||||||
|
nla_for_each_nested(af, tb[IFLA_AF_SPEC], rem) {
|
||||||
|
const struct rtnl_af_ops *af_ops;
|
||||||
|
|
||||||
|
if (!(af_ops = rtnl_af_lookup(nla_type(af))))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (!af_ops->parse_link_af)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
err = af_ops->parse_link_af(dev, af);
|
||||||
|
if (err < 0)
|
||||||
|
goto errout;
|
||||||
|
|
||||||
|
modified = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
err = 0;
|
err = 0;
|
||||||
|
|
||||||
errout:
|
errout:
|
||||||
|
|
Loading…
Reference in New Issue