Commit f8ff182c authored by Thomas Graf's avatar Thomas Graf Committed by David S. Miller

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: default avatarThomas Graf <tgraf@infradead.org>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent d67ef35f
......@@ -80,6 +80,24 @@ struct rtnl_link_ifmap {
__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 {
IFLA_UNSPEC,
IFLA_ADDRESS,
......@@ -116,6 +134,7 @@ enum {
IFLA_STATS64,
IFLA_VF_PORTS,
IFLA_PORT_SELF,
IFLA_AF_SPEC,
__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 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_device *rtnl_create_link(struct net *src_net, struct net *net,
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;
}
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)
{
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 */
+ rtnl_vfinfo_size(dev) /* IFLA_VFINFO_LIST */
+ 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)
......@@ -757,7 +847,8 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
struct nlmsghdr *nlh;
struct rtnl_link_stats64 temp;
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);
if (nlh == NULL)
......@@ -866,6 +957,36 @@ static int rtnl_fill_ifinfo(struct sk_buff *skb, struct net_device *dev,
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);
nla_put_failure:
......@@ -924,6 +1045,7 @@ const struct nla_policy ifla_policy[IFLA_MAX+1] = {
[IFLA_VFINFO_LIST] = {. type = NLA_NESTED },
[IFLA_VF_PORTS] = { .type = NLA_NESTED },
[IFLA_PORT_SELF] = { .type = NLA_NESTED },
[IFLA_AF_SPEC] = { .type = NLA_NESTED },
};
EXPORT_SYMBOL(ifla_policy);
......@@ -1225,6 +1347,27 @@ static int do_setlink(struct net_device *dev, struct ifinfomsg *ifm,
goto errout;
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;
errout:
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment