Skip to content

Commit

Permalink
rtnetlink: Link address family API
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Thomas Graf authored and David S. Miller committed Nov 17, 2010
1 parent d67ef35 commit f8ff182
Show file tree
Hide file tree
Showing 3 changed files with 195 additions and 2 deletions.
19 changes: 19 additions & 0 deletions include/linux/if_link.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -116,6 +134,7 @@ enum {
IFLA_STATS64,
IFLA_VF_PORTS,
IFLA_PORT_SELF,
IFLA_AF_SPEC,
__IFLA_MAX
};

Expand Down
31 changes: 31 additions & 0 deletions include/net/rtnetlink.h
Original file line number Diff line number Diff line change
Expand Up @@ -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[]);
Expand Down
147 changes: 145 additions & 2 deletions net/core/rtnetlink.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit f8ff182

Please sign in to comment.