/*
 * lib/route/link.c	Links (Interfaces)
 *
 *	This library is free software; you can redistribute it and/or
 *	modify it under the terms of the GNU Lesser General Public
 *	License as published by the Free Software Foundation version 2.1
 *	of the License.
 *
 * Copyright (c) 2003-2010 Thomas Graf <tgraf@suug.ch>
 */

/**
 * @ingroup rtnl
 * @defgroup link Links (Interfaces)
 * @brief
 *
 * @par Link Identification
 * A link can be identified by either its interface index or by its
 * name. The kernel favours the interface index but falls back to the
 * interface name if the interface index is lesser-than 0 for kernels
 * >= 2.6.11. Therefore you can request changes without mapping a
 * interface name to the corresponding index first.
 *
 * @par Changeable Attributes
 * @anchor link_changeable
 *  - Link layer address
 *  - Link layer broadcast address
 *  - device mapping (ifmap) (>= 2.6.9)
 *  - MTU (>= 2.6.9)
 *  - Transmission queue length (>= 2.6.9)
 *  - Weight (>= 2.6.9)
 *  - Link name (only via access through interface index) (>= 2.6.9)
 *  - Flags (>= 2.6.9)
 *    - IFF_DEBUG
 *    - IFF_NOTRAILERS
 *    - IFF_NOARP
 *    - IFF_DYNAMIC
 *    - IFF_MULTICAST
 *    - IFF_PORTSEL
 *    - IFF_AUTOMEDIA
 *    - IFF_UP
 *    - IFF_PROMISC
 *    - IFF_ALLMULTI
 *
 * @par Link Flags (linux/if.h)
 * @anchor link_flags
 * @code
 *   IFF_UP            Status of link (up|down)
 *   IFF_BROADCAST     Indicates this link allows broadcasting
 *   IFF_MULTICAST     Indicates this link allows multicasting
 *   IFF_ALLMULTI      Indicates this link is doing multicast routing
 *   IFF_DEBUG         Tell the driver to do debugging (currently unused)
 *   IFF_LOOPBACK      This is the loopback link
 *   IFF_POINTOPOINT   Point-to-point link
 *   IFF_NOARP         Link is unable to perform ARP
 *   IFF_PROMISC       Status of promiscious mode flag
 *   IFF_MASTER        Used by teql
 *   IFF_SLAVE         Used by teql
 *   IFF_PORTSEL       Indicates this link allows port selection
 *   IFF_AUTOMEDIA     Indicates this link selects port automatically
 *   IFF_DYNAMIC       Indicates the address of this link is dynamic
 *   IFF_RUNNING       Link is running and carrier is ok.
 *   IFF_NOTRAILERS    Unused, BSD compat.
 * @endcode
 *
 * @par Notes on IFF_PROMISC and IFF_ALLMULTI flags
 * Although you can query the status of IFF_PROMISC and IFF_ALLMULTI
 * they do not represent the actual state in the kernel but rather
 * whether the flag has been enabled/disabled by userspace. The link
 * may be in promiscious mode even if IFF_PROMISC is not set in a link
 * dump request response because promiscity might be needed by the driver
 * for a period of time.
 *
 * @note The unit of the transmission queue length depends on the
 *       link type, a common unit is \a packets.
 *
 * @par 1) Retrieving information about available links
 * @code
 * // The first step is to retrieve a list of all available interfaces within
 * // the kernel and put them into a cache.
 * struct nl_cache *cache = rtnl_link_alloc_cache(sk);
 *
 * // In a second step, a specific link may be looked up by either interface
 * // index or interface name.
 * struct rtnl_link *link = rtnl_link_get_by_name(cache, "lo");
 *
 * // rtnl_link_get_by_name() is the short version for translating the
 * // interface name to an interface index first like this:
 * int ifindex = rtnl_link_name2i(cache, "lo");
 * struct rtnl_link *link = rtnl_link_get(cache, ifindex);
 *
 * // After successful usage, the object must be given back to the cache
 * rtnl_link_put(link);
 * @endcode
 *
 * @par 2) Changing link attributes
 * @code
 * // In order to change any attributes of an existing link, we must allocate
 * // a new link to hold the change requests:
 * struct rtnl_link *request = rtnl_link_alloc();
 *
 * // Now we can go on and specify the attributes we want to change:
 * rtnl_link_set_weight(request, 300);
 * rtnl_link_set_mtu(request, 1360);
 *
 * // We can also shut an interface down administratively
 * rtnl_link_unset_flags(request, rtnl_link_str2flags("up"));
 *
 * // Actually, we should know which link to change, so let's look it up
 * struct rtnl_link *old = rtnl_link_get(cache, "eth0");
 *
 * // Two ways exist to commit this change request, the first one is to
 * // build the required netlink message and send it out in one single
 * // step:
 * rtnl_link_change(sk, old, request, 0);
 *
 * // An alternative way is to build the netlink message and send it
 * // out yourself using nl_send_auto_complete()
 * struct nl_msg *msg = rtnl_link_build_change_request(old, request);
 * nl_send_auto_complete(sk, nlmsg_hdr(msg));
 * nlmsg_free(msg);
 *
 * // Don't forget to give back the link object ;->
 * rtnl_link_put(old);
 * @endcode
 *
 * @par 3) Link Type Specific Attributes
 * @code
 * // Some link types offer additional parameters and statistics specific
 * // to their type. F.e. a VLAN link can be configured like this:
 * //
 * // Allocate a new link and set the info type to "vlan". This is required
 * // to prepare the link to hold vlan specific attributes.
 * struct rtnl_link *request = rtnl_link_alloc();
 * rtnl_link_set_info_type(request, "vlan");
 *
 * // Now vlan specific attributes can be set:
 * rtnl_link_vlan_set_id(request, 10);
 * rtnl_link_vlan_set_ingress_map(request, 2, 8);
 *
 * // Of course the attributes can also be read, check the info type
 * // to make sure you are using the right access functions:
 * char *type = rtnl_link_get_info_type(link);
 * if (!strcmp(type, "vlan"))
 * 	int id = rtnl_link_vlan_get_id(link);
 * @endcode
 * @{
 */

#include <netlink-local.h>
#include <netlink/netlink.h>
#include <netlink/attr.h>
#include <netlink/utils.h>
#include <netlink/object.h>
#include <netlink/route/rtnl.h>
#include <netlink/route/link.h>
#include <netlink/route/link/api.h>

/** @cond SKIP */
#define LINK_ATTR_MTU     0x0001
#define LINK_ATTR_LINK    0x0002
#define LINK_ATTR_TXQLEN  0x0004
#define LINK_ATTR_WEIGHT  0x0008
#define LINK_ATTR_MASTER  0x0010
#define LINK_ATTR_QDISC   0x0020
#define LINK_ATTR_MAP     0x0040
#define LINK_ATTR_ADDR    0x0080
#define LINK_ATTR_BRD     0x0100
#define LINK_ATTR_FLAGS   0x0200
#define LINK_ATTR_IFNAME  0x0400
#define LINK_ATTR_IFINDEX 0x0800
#define LINK_ATTR_FAMILY  0x1000
#define LINK_ATTR_ARPTYPE 0x2000
#define LINK_ATTR_STATS   0x4000
#define LINK_ATTR_CHANGE  0x8000
#define LINK_ATTR_OPERSTATE 0x10000
#define LINK_ATTR_LINKMODE  0x20000
#define LINK_ATTR_LINKINFO  0x40000
#define LINK_ATTR_IFALIAS   0x80000
#define LINK_ATTR_NUM_VF   0x100000

static struct nl_cache_ops rtnl_link_ops;
static struct nl_object_ops link_obj_ops;
/** @endcond */

static struct rtnl_link_af_ops *af_lookup_and_alloc(struct rtnl_link *link,
						    int family)
{
	struct rtnl_link_af_ops *af_ops;
	void *data;

	af_ops = rtnl_link_af_ops_lookup(family);
	if (!af_ops)
		return NULL;

	if (!(data = rtnl_link_af_alloc(link, af_ops)))
		return NULL;

	return af_ops;
}

static int af_free(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
		    void *data, void *arg)
{
	if (ops->ao_free)
		ops->ao_free(link, data);

	rtnl_link_af_ops_put(ops);

	return 0;
}

static int af_clone(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
		    void *data, void *arg)
{
	struct rtnl_link *dst = arg;

	if (ops->ao_clone &&
	    !(dst->l_af_data[ops->ao_family] = ops->ao_clone(dst, data)))
		return -NLE_NOMEM;

	return 0;
}

static int af_fill(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
		   void *data, void *arg)
{
	struct nl_msg *msg = arg;
	struct nlattr *af_attr;
	int err;

	if (!ops->ao_fill_af)
		return 0;

	if (!(af_attr = nla_nest_start(msg, ops->ao_family)))
		return -NLE_MSGSIZE;

	if ((err = ops->ao_fill_af(link, arg, data)) < 0)
		return err;

	nla_nest_end(msg, af_attr);

	return 0;
}

static int af_dump_line(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
			 void *data, void *arg)
{
	struct nl_dump_params *p = arg;

	if (ops->ao_dump[NL_DUMP_LINE])
		ops->ao_dump[NL_DUMP_LINE](link, p, data);

	return 0;
}

static int af_dump_details(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
			   void *data, void *arg)
{
	struct nl_dump_params *p = arg;

	if (ops->ao_dump[NL_DUMP_DETAILS])
		ops->ao_dump[NL_DUMP_DETAILS](link, p, data);

	return 0;
}

static int af_dump_stats(struct rtnl_link *link, struct rtnl_link_af_ops *ops,
			 void *data, void *arg)
{
	struct nl_dump_params *p = arg;

	if (ops->ao_dump[NL_DUMP_STATS])
		ops->ao_dump[NL_DUMP_STATS](link, p, data);

	return 0;
}

static int do_foreach_af(struct rtnl_link *link,
			 int (*cb)(struct rtnl_link *,
			 	   struct rtnl_link_af_ops *, void *, void *),
			 void *arg)
{
	int i, err;

	for (i = 0; i < AF_MAX; i++) {
		if (link->l_af_data[i]) {
			struct rtnl_link_af_ops *ops;

			if (!(ops = rtnl_link_af_ops_lookup(i)))
				BUG();

			if ((err = cb(link, ops, link->l_af_data[i], arg)) < 0)
				return err;
		}
	}

	return 0;
}

static void release_link_info(struct rtnl_link *link)
{
	struct rtnl_link_info_ops *io = link->l_info_ops;

	if (io != NULL) {
		if (io->io_free)
			io->io_free(link);
		rtnl_link_info_ops_put(io);
		link->l_info_ops = NULL;
	}
}

static void link_free_data(struct nl_object *c)
{
	struct rtnl_link *link = nl_object_priv(c);

	if (link) {
		struct rtnl_link_info_ops *io;

		if ((io = link->l_info_ops) != NULL)
			release_link_info(link);

		nl_addr_put(link->l_addr);
		nl_addr_put(link->l_bcast);

		free(link->l_ifalias);

		do_foreach_af(link, af_free, NULL);
	}
}

static int link_clone(struct nl_object *_dst, struct nl_object *_src)
{
	struct rtnl_link *dst = nl_object_priv(_dst);
	struct rtnl_link *src = nl_object_priv(_src);
	int err;

	if (src->l_addr)
		if (!(dst->l_addr = nl_addr_clone(src->l_addr)))
			return -NLE_NOMEM;

	if (src->l_bcast)
		if (!(dst->l_bcast = nl_addr_clone(src->l_bcast)))
			return -NLE_NOMEM;

	if (src->l_ifalias)
		if (!(dst->l_ifalias = strdup(src->l_ifalias)))
			return -NLE_NOMEM;

	if (src->l_info_ops && src->l_info_ops->io_clone) {
		err = src->l_info_ops->io_clone(dst, src);
		if (err < 0)
			return err;
	}

	if ((err = do_foreach_af(src, af_clone, dst)) < 0)
		return err;

	return 0;
}

static struct nla_policy link_policy[IFLA_MAX+1] = {
	[IFLA_IFNAME]	= { .type = NLA_STRING,
			    .maxlen = IFNAMSIZ },
	[IFLA_MTU]	= { .type = NLA_U32 },
	[IFLA_TXQLEN]	= { .type = NLA_U32 },
	[IFLA_LINK]	= { .type = NLA_U32 },
	[IFLA_WEIGHT]	= { .type = NLA_U32 },
	[IFLA_MASTER]	= { .type = NLA_U32 },
	[IFLA_OPERSTATE]= { .type = NLA_U8 },
	[IFLA_LINKMODE] = { .type = NLA_U8 },
	[IFLA_LINKINFO]	= { .type = NLA_NESTED },
	[IFLA_QDISC]	= { .type = NLA_STRING,
			    .maxlen = IFQDISCSIZ },
	[IFLA_STATS]	= { .minlen = sizeof(struct rtnl_link_stats) },
	[IFLA_STATS64]	= { .minlen = sizeof(struct rtnl_link_stats64) },
	[IFLA_MAP]	= { .minlen = sizeof(struct rtnl_link_ifmap) },
	[IFLA_IFALIAS]	= { .type = NLA_STRING, .maxlen = IFALIASZ },
	[IFLA_NUM_VF]	= { .type = NLA_U32 },
	[IFLA_AF_SPEC]	= { .type = NLA_NESTED },
};

static struct nla_policy link_info_policy[IFLA_INFO_MAX+1] = {
	[IFLA_INFO_KIND]	= { .type = NLA_STRING },
	[IFLA_INFO_DATA]	= { .type = NLA_NESTED },
	[IFLA_INFO_XSTATS]	= { .type = NLA_NESTED },
};

static int link_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
			   struct nlmsghdr *n, struct nl_parser_param *pp)
{
	struct rtnl_link *link;
	struct ifinfomsg *ifi;
	struct nlattr *tb[IFLA_MAX+1];
	struct rtnl_link_af_ops *af_ops = NULL;
	int err, family;

	link = rtnl_link_alloc();
	if (link == NULL) {
		err = -NLE_NOMEM;
		goto errout;
	}
		
	link->ce_msgtype = n->nlmsg_type;

	if (!nlmsg_valid_hdr(n, sizeof(*ifi)))
		return -NLE_MSG_TOOSHORT;

	ifi = nlmsg_data(n);
	link->l_family = family = ifi->ifi_family;
	link->l_arptype = ifi->ifi_type;
	link->l_index = ifi->ifi_index;
	link->l_flags = ifi->ifi_flags;
	link->l_change = ifi->ifi_change;
	link->ce_mask = (LINK_ATTR_IFNAME | LINK_ATTR_FAMILY |
			  LINK_ATTR_ARPTYPE| LINK_ATTR_IFINDEX |
			  LINK_ATTR_FLAGS | LINK_ATTR_CHANGE);

	if ((af_ops = af_lookup_and_alloc(link, family))) {
		if (af_ops->ao_protinfo_policy) {
			memcpy(&link_policy[IFLA_PROTINFO],
			       af_ops->ao_protinfo_policy,
			       sizeof(struct nla_policy));
		}
	}

	err = nlmsg_parse(n, sizeof(*ifi), tb, IFLA_MAX, link_policy);
	if (err < 0)
		goto errout;

	if (tb[IFLA_IFNAME] == NULL) {
		err = -NLE_MISSING_ATTR;
		goto errout;
	}

	nla_strlcpy(link->l_name, tb[IFLA_IFNAME], IFNAMSIZ);


	if (tb[IFLA_STATS]) {
		struct rtnl_link_stats *st = nla_data(tb[IFLA_STATS]);

		link->l_stats[RTNL_LINK_RX_PACKETS]	= st->rx_packets;
		link->l_stats[RTNL_LINK_TX_PACKETS]	= st->tx_packets;
		link->l_stats[RTNL_LINK_RX_BYTES]	= st->rx_bytes;
		link->l_stats[RTNL_LINK_TX_BYTES]	= st->tx_bytes;
		link->l_stats[RTNL_LINK_RX_ERRORS]	= st->rx_errors;
		link->l_stats[RTNL_LINK_TX_ERRORS]	= st->tx_errors;
		link->l_stats[RTNL_LINK_RX_DROPPED]	= st->rx_dropped;
		link->l_stats[RTNL_LINK_TX_DROPPED]	= st->tx_dropped;
		link->l_stats[RTNL_LINK_MULTICAST]	= st->multicast;
		link->l_stats[RTNL_LINK_COLLISIONS]	= st->collisions;

		link->l_stats[RTNL_LINK_RX_LEN_ERR]	= st->rx_length_errors;
		link->l_stats[RTNL_LINK_RX_OVER_ERR]	= st->rx_over_errors;
		link->l_stats[RTNL_LINK_RX_CRC_ERR]	= st->rx_crc_errors;
		link->l_stats[RTNL_LINK_RX_FRAME_ERR]	= st->rx_frame_errors;
		link->l_stats[RTNL_LINK_RX_FIFO_ERR]	= st->rx_fifo_errors;
		link->l_stats[RTNL_LINK_RX_MISSED_ERR]	= st->rx_missed_errors;

		link->l_stats[RTNL_LINK_TX_ABORT_ERR]	= st->tx_aborted_errors;
		link->l_stats[RTNL_LINK_TX_CARRIER_ERR]	= st->tx_carrier_errors;
		link->l_stats[RTNL_LINK_TX_FIFO_ERR]	= st->tx_fifo_errors;
		link->l_stats[RTNL_LINK_TX_HBEAT_ERR]	= st->tx_heartbeat_errors;
		link->l_stats[RTNL_LINK_TX_WIN_ERR]	= st->tx_window_errors;

		link->l_stats[RTNL_LINK_RX_COMPRESSED]	= st->rx_compressed;
		link->l_stats[RTNL_LINK_TX_COMPRESSED]	= st->tx_compressed;

		link->ce_mask |= LINK_ATTR_STATS;
	}

	if (tb[IFLA_STATS64]) {
		struct rtnl_link_stats64 *st = nla_data(tb[IFLA_STATS64]);
		
		link->l_stats[RTNL_LINK_RX_PACKETS]	= st->rx_packets;
		link->l_stats[RTNL_LINK_TX_PACKETS]	= st->tx_packets;
		link->l_stats[RTNL_LINK_RX_BYTES]	= st->rx_bytes;
		link->l_stats[RTNL_LINK_TX_BYTES]	= st->tx_bytes;
		link->l_stats[RTNL_LINK_RX_ERRORS]	= st->rx_errors;
		link->l_stats[RTNL_LINK_TX_ERRORS]	= st->tx_errors;
		link->l_stats[RTNL_LINK_RX_DROPPED]	= st->rx_dropped;
		link->l_stats[RTNL_LINK_TX_DROPPED]	= st->tx_dropped;
		link->l_stats[RTNL_LINK_MULTICAST]	= st->multicast;
		link->l_stats[RTNL_LINK_COLLISIONS]	= st->collisions;

		link->l_stats[RTNL_LINK_RX_LEN_ERR]	= st->rx_length_errors;
		link->l_stats[RTNL_LINK_RX_OVER_ERR]	= st->rx_over_errors;
		link->l_stats[RTNL_LINK_RX_CRC_ERR]	= st->rx_crc_errors;
		link->l_stats[RTNL_LINK_RX_FRAME_ERR]	= st->rx_frame_errors;
		link->l_stats[RTNL_LINK_RX_FIFO_ERR]	= st->rx_fifo_errors;
		link->l_stats[RTNL_LINK_RX_MISSED_ERR]	= st->rx_missed_errors;

		link->l_stats[RTNL_LINK_TX_ABORT_ERR]	= st->tx_aborted_errors;
		link->l_stats[RTNL_LINK_TX_CARRIER_ERR]	= st->tx_carrier_errors;
		link->l_stats[RTNL_LINK_TX_FIFO_ERR]	= st->tx_fifo_errors;
		link->l_stats[RTNL_LINK_TX_HBEAT_ERR]	= st->tx_heartbeat_errors;
		link->l_stats[RTNL_LINK_TX_WIN_ERR]	= st->tx_window_errors;

		link->l_stats[RTNL_LINK_RX_COMPRESSED]	= st->rx_compressed;
		link->l_stats[RTNL_LINK_TX_COMPRESSED]	= st->tx_compressed;

		link->ce_mask |= LINK_ATTR_STATS;
	}

	if (tb[IFLA_TXQLEN]) {
		link->l_txqlen = nla_get_u32(tb[IFLA_TXQLEN]);
		link->ce_mask |= LINK_ATTR_TXQLEN;
	}

	if (tb[IFLA_MTU]) {
		link->l_mtu = nla_get_u32(tb[IFLA_MTU]);
		link->ce_mask |= LINK_ATTR_MTU;
	}

	if (tb[IFLA_ADDRESS]) {
		link->l_addr = nl_addr_alloc_attr(tb[IFLA_ADDRESS], AF_UNSPEC);
		if (link->l_addr == NULL) {
			err = -NLE_NOMEM;
			goto errout;
		}
		nl_addr_set_family(link->l_addr,
				   nl_addr_guess_family(link->l_addr));
		link->ce_mask |= LINK_ATTR_ADDR;
	}

	if (tb[IFLA_BROADCAST]) {
		link->l_bcast = nl_addr_alloc_attr(tb[IFLA_BROADCAST],
						   AF_UNSPEC);
		if (link->l_bcast == NULL) {
			err = -NLE_NOMEM;
			goto errout;
		}
		nl_addr_set_family(link->l_bcast,
				   nl_addr_guess_family(link->l_bcast));
		link->ce_mask |= LINK_ATTR_BRD;
	}

	if (tb[IFLA_LINK]) {
		link->l_link = nla_get_u32(tb[IFLA_LINK]);
		link->ce_mask |= LINK_ATTR_LINK;
	}

	if (tb[IFLA_WEIGHT]) {
		link->l_weight = nla_get_u32(tb[IFLA_WEIGHT]);
		link->ce_mask |= LINK_ATTR_WEIGHT;
	}

	if (tb[IFLA_QDISC]) {
		nla_strlcpy(link->l_qdisc, tb[IFLA_QDISC], IFQDISCSIZ);
		link->ce_mask |= LINK_ATTR_QDISC;
	}

	if (tb[IFLA_MAP]) {
		nla_memcpy(&link->l_map, tb[IFLA_MAP], 
			   sizeof(struct rtnl_link_ifmap));
		link->ce_mask |= LINK_ATTR_MAP;
	}

	if (tb[IFLA_MASTER]) {
		link->l_master = nla_get_u32(tb[IFLA_MASTER]);
		link->ce_mask |= LINK_ATTR_MASTER;
	}

	if (tb[IFLA_OPERSTATE]) {
		link->l_operstate = nla_get_u8(tb[IFLA_OPERSTATE]);
		link->ce_mask |= LINK_ATTR_OPERSTATE;
	}

	if (tb[IFLA_LINKMODE]) {
		link->l_linkmode = nla_get_u8(tb[IFLA_LINKMODE]);
		link->ce_mask |= LINK_ATTR_LINKMODE;
	}

	if (tb[IFLA_IFALIAS]) {
		link->l_ifalias = nla_strdup(tb[IFLA_IFALIAS]);
		if (link->l_ifalias == NULL) {
			err = -NLE_NOMEM;
			goto errout;
		}
		link->ce_mask |= LINK_ATTR_IFALIAS;
	}

	if (tb[IFLA_NUM_VF]) {
		link->l_num_vf = nla_get_u32(tb[IFLA_NUM_VF]);
		link->ce_mask |= LINK_ATTR_NUM_VF;
	}

	if (tb[IFLA_LINKINFO]) {
		struct nlattr *li[IFLA_INFO_MAX+1];

		err = nla_parse_nested(li, IFLA_INFO_MAX, tb[IFLA_LINKINFO],
				       link_info_policy);
		if (err < 0)
			goto errout;

		if (li[IFLA_INFO_KIND]) {
			struct rtnl_link_info_ops *ops;
			char *kind;

			kind = nla_get_string(li[IFLA_INFO_KIND]);
			ops = rtnl_link_info_ops_lookup(kind);
			link->l_info_ops = ops;
			
			if (ops && ops->io_parse &&
			    (li[IFLA_INFO_DATA] || li[IFLA_INFO_XSTATS])) {
				err = ops->io_parse(link, li[IFLA_INFO_DATA],
						    li[IFLA_INFO_XSTATS]);
				if (err < 0)
					goto errout;
			} else {
				/* XXX: Warn about unparsed info? */
			}
		}
	}

	if (tb[IFLA_PROTINFO] && af_ops && af_ops->ao_parse_protinfo) {
		err = af_ops->ao_parse_protinfo(link, tb[IFLA_PROTINFO],
						link->l_af_data[link->l_family]);
		if (err < 0)
			goto errout;
	}

	if (tb[IFLA_AF_SPEC]) {
		struct nlattr *af_attr;
		int remaining;

		nla_for_each_nested(af_attr, tb[IFLA_AF_SPEC], remaining) {
			af_ops = af_lookup_and_alloc(link, nla_type(af_attr));
			if (af_ops && af_ops->ao_parse_af) {
				char *af_data = link->l_af_data[nla_type(af_attr)];

				err = af_ops->ao_parse_af(link, af_attr, af_data);

				rtnl_link_af_ops_put(af_ops);

				if (err < 0)
					goto errout;
			}

		}
	}

	err = pp->pp_cb((struct nl_object *) link, pp);
errout:
	rtnl_link_af_ops_put(af_ops);
	rtnl_link_put(link);
	return err;
}

static int link_request_update(struct nl_cache *cache, struct nl_sock *sk)
{
	int family = cache->c_iarg1;

	return nl_rtgen_request(sk, RTM_GETLINK, family, NLM_F_DUMP);
}

static void link_dump_line(struct nl_object *obj, struct nl_dump_params *p)
{
	char buf[128];
	struct nl_cache *cache = dp_cache(obj);
	struct rtnl_link *link = (struct rtnl_link *) obj;

	nl_dump_line(p, "%s %s ", link->l_name,
		     nl_llproto2str(link->l_arptype, buf, sizeof(buf)));

	if (link->l_addr && !nl_addr_iszero(link->l_addr))
		nl_dump(p, "%s ", nl_addr2str(link->l_addr, buf, sizeof(buf)));

	if (link->ce_mask & LINK_ATTR_MASTER) {
		struct rtnl_link *master = rtnl_link_get(cache, link->l_master);
		nl_dump(p, "master %s ", master ? master->l_name : "inv");
		if (master)
			rtnl_link_put(master);
	}

	rtnl_link_flags2str(link->l_flags, buf, sizeof(buf));
	if (buf[0])
		nl_dump(p, "<%s> ", buf);

	if (link->ce_mask & LINK_ATTR_LINK) {
		struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
		nl_dump(p, "slave-of %s ", ll ? ll->l_name : "NONE");
		if (ll)
			rtnl_link_put(ll);
	}

	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_LINE])
		link->l_info_ops->io_dump[NL_DUMP_LINE](link, p);

	do_foreach_af(link, af_dump_line, p);

	nl_dump(p, "\n");
}

static void link_dump_details(struct nl_object *obj, struct nl_dump_params *p)
{
	struct rtnl_link *link = (struct rtnl_link *) obj;
	char buf[64];

	link_dump_line(obj, p);

	nl_dump_line(p, "    mtu %u ", link->l_mtu);
	nl_dump(p, "txqlen %u weight %u ", link->l_txqlen, link->l_weight);

	if (link->ce_mask & LINK_ATTR_QDISC)
		nl_dump(p, "qdisc %s ", link->l_qdisc);

	if (link->ce_mask & LINK_ATTR_MAP && link->l_map.lm_irq)
		nl_dump(p, "irq %u ", link->l_map.lm_irq);

	if (link->ce_mask & LINK_ATTR_IFINDEX)
		nl_dump(p, "index %u ", link->l_index);


	nl_dump(p, "\n");

	if (link->ce_mask & LINK_ATTR_IFALIAS)
		nl_dump_line(p, "    alias %s\n", link->l_ifalias);

	nl_dump_line(p, "    ");

	if (link->ce_mask & LINK_ATTR_BRD)
		nl_dump(p, "brd %s ", nl_addr2str(link->l_bcast, buf,
						   sizeof(buf)));

	if ((link->ce_mask & LINK_ATTR_OPERSTATE) &&
	    link->l_operstate != IF_OPER_UNKNOWN) {
		rtnl_link_operstate2str(link->l_operstate, buf, sizeof(buf));
		nl_dump(p, "state %s ", buf);
	}

	if (link->ce_mask & LINK_ATTR_NUM_VF)
		nl_dump(p, "num-vf %u ", link->l_num_vf);

	nl_dump(p, "mode %s\n",
		rtnl_link_mode2str(link->l_linkmode, buf, sizeof(buf)));

	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_DETAILS])
		link->l_info_ops->io_dump[NL_DUMP_DETAILS](link, p);

	do_foreach_af(link, af_dump_details, p);
}

static void link_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
{
	struct rtnl_link *link = (struct rtnl_link *) obj;
	char *unit, fmt[64];
	float res;
	
	link_dump_details(obj, p);

	nl_dump_line(p, "    Stats:    bytes    packets     errors "
			"   dropped   fifo-err compressed\n");

	res = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_RX_BYTES], &unit);

	strcpy(fmt, "     RX %X.2f %s %10llu %10llu %10llu %10llu %10llu\n");
	fmt[9] = *unit == 'B' ? '9' : '7';
	
	nl_dump_line(p, fmt, res, unit,
		link->l_stats[RTNL_LINK_RX_PACKETS],
		link->l_stats[RTNL_LINK_RX_ERRORS],
		link->l_stats[RTNL_LINK_RX_DROPPED],
		link->l_stats[RTNL_LINK_RX_FIFO_ERR],
		link->l_stats[RTNL_LINK_RX_COMPRESSED]);

	res = nl_cancel_down_bytes(link->l_stats[RTNL_LINK_TX_BYTES], &unit);

	strcpy(fmt, "     TX %X.2f %s %10llu %10llu %10llu %10llu %10llu\n");
	fmt[9] = *unit == 'B' ? '9' : '7';
	
	nl_dump_line(p, fmt, res, unit,
		link->l_stats[RTNL_LINK_TX_PACKETS],
		link->l_stats[RTNL_LINK_TX_ERRORS],
		link->l_stats[RTNL_LINK_TX_DROPPED],
		link->l_stats[RTNL_LINK_TX_FIFO_ERR],
		link->l_stats[RTNL_LINK_TX_COMPRESSED]);

	nl_dump_line(p, "    Errors:  length       over        crc "
			"     frame     missed  multicast\n");

	nl_dump_line(p, "     RX  %10" PRIu64 " %10" PRIu64 " %10"
				PRIu64 " %10" PRIu64 " %10" PRIu64 " %10"
				PRIu64 "\n",
		link->l_stats[RTNL_LINK_RX_LEN_ERR],
		link->l_stats[RTNL_LINK_RX_OVER_ERR],
		link->l_stats[RTNL_LINK_RX_CRC_ERR],
		link->l_stats[RTNL_LINK_RX_FRAME_ERR],
		link->l_stats[RTNL_LINK_RX_MISSED_ERR],
		link->l_stats[RTNL_LINK_MULTICAST]);

	nl_dump_line(p, "            aborted    carrier  heartbeat "
			"    window  collision\n");
	
	nl_dump_line(p, "     TX  %10" PRIu64 " %10" PRIu64 " %10"
			PRIu64 " %10" PRIu64 " %10" PRIu64 "\n",
		link->l_stats[RTNL_LINK_TX_ABORT_ERR],
		link->l_stats[RTNL_LINK_TX_CARRIER_ERR],
		link->l_stats[RTNL_LINK_TX_HBEAT_ERR],
		link->l_stats[RTNL_LINK_TX_WIN_ERR],
		link->l_stats[RTNL_LINK_COLLISIONS]);

	if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_STATS])
		link->l_info_ops->io_dump[NL_DUMP_STATS](link, p);

	do_foreach_af(link, af_dump_stats, p);
}

#if 0
static int link_handle_event(struct nl_object *a, struct rtnl_link_event_cb *cb)
{
	struct rtnl_link *l = (struct rtnl_link *) a;
	struct nl_cache *c = dp_cache(a);
	int nevents = 0;

	if (l->l_change == ~0U) {
		if (l->ce_msgtype == RTM_NEWLINK)
			cb->le_register(l);
		else
			cb->le_unregister(l);

		return 1;
	}

	if (l->l_change & IFF_SLAVE) {
		if (l->l_flags & IFF_SLAVE) {
			struct rtnl_link *m = rtnl_link_get(c, l->l_master);
			cb->le_new_bonding(l, m);
			if (m)
				rtnl_link_put(m);
		} else
			cb->le_cancel_bonding(l);
	}

#if 0
	if (l->l_change & IFF_UP && l->l_change & IFF_RUNNING)
		dp_dump_line(p, line++, "link %s changed state to %s.\n",
			l->l_name, l->l_flags & IFF_UP ? "up" : "down");

	if (l->l_change & IFF_PROMISC) {
		dp_new_line(p, line++);
		dp_dump(p, "link %s %s promiscuous mode.\n",
		    l->l_name, l->l_flags & IFF_PROMISC ? "entered" : "left");
	}

	if (line == 0)
		dp_dump_line(p, line++, "link %s sent unknown event.\n",
			     l->l_name);
#endif

	return nevents;
}
#endif

static int link_compare(struct nl_object *_a, struct nl_object *_b,
			uint32_t attrs, int flags)
{
	struct rtnl_link *a = (struct rtnl_link *) _a;
	struct rtnl_link *b = (struct rtnl_link *) _b;
	int diff = 0;

#define LINK_DIFF(ATTR, EXPR) ATTR_DIFF(attrs, LINK_ATTR_##ATTR, a, b, EXPR)

	diff |= LINK_DIFF(IFINDEX,	a->l_index != b->l_index);
	diff |= LINK_DIFF(MTU,		a->l_mtu != b->l_mtu);
	diff |= LINK_DIFF(LINK,		a->l_link != b->l_link);
	diff |= LINK_DIFF(TXQLEN,	a->l_txqlen != b->l_txqlen);
	diff |= LINK_DIFF(WEIGHT,	a->l_weight != b->l_weight);
	diff |= LINK_DIFF(MASTER,	a->l_master != b->l_master);
	diff |= LINK_DIFF(FAMILY,	a->l_family != b->l_family);
	diff |= LINK_DIFF(OPERSTATE,	a->l_operstate != b->l_operstate);
	diff |= LINK_DIFF(LINKMODE,	a->l_linkmode != b->l_linkmode);
	diff |= LINK_DIFF(QDISC,	strcmp(a->l_qdisc, b->l_qdisc));
	diff |= LINK_DIFF(IFNAME,	strcmp(a->l_name, b->l_name));
	diff |= LINK_DIFF(ADDR,		nl_addr_cmp(a->l_addr, b->l_addr));
	diff |= LINK_DIFF(BRD,		nl_addr_cmp(a->l_bcast, b->l_bcast));
	diff |= LINK_DIFF(IFALIAS,	strcmp(a->l_ifalias, b->l_ifalias));
	diff |= LINK_DIFF(NUM_VF,	a->l_num_vf != b->l_num_vf);

	if (flags & LOOSE_COMPARISON)
		diff |= LINK_DIFF(FLAGS,
				  (a->l_flags ^ b->l_flags) & b->l_flag_mask);
	else
		diff |= LINK_DIFF(FLAGS, a->l_flags != b->l_flags);

#undef LINK_DIFF

	return diff;
}

static const struct trans_tbl link_attrs[] = {
	__ADD(LINK_ATTR_MTU, mtu)
	__ADD(LINK_ATTR_LINK, link)
	__ADD(LINK_ATTR_TXQLEN, txqlen)
	__ADD(LINK_ATTR_WEIGHT, weight)
	__ADD(LINK_ATTR_MASTER, master)
	__ADD(LINK_ATTR_QDISC, qdisc)
	__ADD(LINK_ATTR_MAP, map)
	__ADD(LINK_ATTR_ADDR, address)
	__ADD(LINK_ATTR_BRD, broadcast)
	__ADD(LINK_ATTR_FLAGS, flags)
	__ADD(LINK_ATTR_IFNAME, name)
	__ADD(LINK_ATTR_IFINDEX, ifindex)
	__ADD(LINK_ATTR_FAMILY, family)
	__ADD(LINK_ATTR_ARPTYPE, arptype)
	__ADD(LINK_ATTR_STATS, stats)
	__ADD(LINK_ATTR_CHANGE, change)
	__ADD(LINK_ATTR_OPERSTATE, operstate)
	__ADD(LINK_ATTR_LINKMODE, linkmode)
	__ADD(LINK_ATTR_IFALIAS, ifalias)
	__ADD(LINK_ATTR_NUM_VF, num_vf)
};

static char *link_attrs2str(int attrs, char *buf, size_t len)
{
	return __flags2str(attrs, buf, len, link_attrs,
			   ARRAY_SIZE(link_attrs));
}

/**
 * @name Allocation/Freeing
 * @{
 */

struct rtnl_link *rtnl_link_alloc(void)
{
	return (struct rtnl_link *) nl_object_alloc(&link_obj_ops);
}

void rtnl_link_put(struct rtnl_link *link)
{
	nl_object_put((struct nl_object *) link);
}

/** @} */

/**
 * @name Cache Management
 * @{
 */


/**
 * Allocate link cache and fill in all configured links.
 * @arg sk		Netlink socket.
 * @arg family		Link address family or AF_UNSPEC
 * @arg result		Pointer to store resulting cache.
 *
 * Allocates a new link cache, initializes it properly and updates it
 * to include all links currently configured in the kernel.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_alloc_cache(struct nl_sock *sk, int family, struct nl_cache **result)
{
	struct nl_cache * cache;
	int err;
	
	cache = nl_cache_alloc(&rtnl_link_ops);
	if (!cache)
		return -NLE_NOMEM;

	cache->c_iarg1 = family;
	
	if (sk && (err = nl_cache_refill(sk, cache)) < 0) {
		nl_cache_free(cache);
		return err;
	}

	*result = cache;
	return 0;
}

/**
 * Look up link by interface index in the provided cache
 * @arg cache		link cache
 * @arg ifindex		link interface index
 *
 * The caller owns a reference on the returned object and
 * must give the object back via rtnl_link_put().
 *
 * @return pointer to link inside the cache or NULL if no match was found.
 */
struct rtnl_link *rtnl_link_get(struct nl_cache *cache, int ifindex)
{
	struct rtnl_link *link;

	if (cache->c_ops != &rtnl_link_ops)
		return NULL;

	nl_list_for_each_entry(link, &cache->c_items, ce_list) {
		if (link->l_index == ifindex) {
			nl_object_get((struct nl_object *) link);
			return link;
		}
	}

	return NULL;
}

/**
 * Look up link by link name in the provided cache
 * @arg cache		link cache
 * @arg name		link name
 *
 * The caller owns a reference on the returned object and
 * must give the object back via rtnl_link_put().
 *
 * @return pointer to link inside the cache or NULL if no match was found.
 */
struct rtnl_link *rtnl_link_get_by_name(struct nl_cache *cache,
					 const char *name)
{
	struct rtnl_link *link;

	if (cache->c_ops != &rtnl_link_ops)
		return NULL;

	nl_list_for_each_entry(link, &cache->c_items, ce_list) {
		if (!strcmp(name, link->l_name)) {
			nl_object_get((struct nl_object *) link);
			return link;
		}
	}

	return NULL;
}

/** @} */

/**
 * @name Link Modifications
 * @{
 */

static int build_link_msg(int cmd, struct ifinfomsg *hdr,
			  struct rtnl_link *link, int flags, struct nl_msg **result)
{
	struct nl_msg *msg;
	struct nlattr *af_spec;

	msg = nlmsg_alloc_simple(cmd, flags);
	if (!msg)
		return -NLE_NOMEM;

	if (nlmsg_append(msg, hdr, sizeof(*hdr), NLMSG_ALIGNTO) < 0)
		goto nla_put_failure;

	if (link->ce_mask & LINK_ATTR_ADDR)
		NLA_PUT_ADDR(msg, IFLA_ADDRESS, link->l_addr);

	if (link->ce_mask & LINK_ATTR_BRD)
		NLA_PUT_ADDR(msg, IFLA_BROADCAST, link->l_bcast);

	if (link->ce_mask & LINK_ATTR_MTU)
		NLA_PUT_U32(msg, IFLA_MTU, link->l_mtu);

	if (link->ce_mask & LINK_ATTR_TXQLEN)
		NLA_PUT_U32(msg, IFLA_TXQLEN, link->l_txqlen);

	if (link->ce_mask & LINK_ATTR_WEIGHT)
		NLA_PUT_U32(msg, IFLA_WEIGHT, link->l_weight);

	if (link->ce_mask & LINK_ATTR_IFNAME)
		NLA_PUT_STRING(msg, IFLA_IFNAME, link->l_name);

	if (link->ce_mask & LINK_ATTR_OPERSTATE)
		NLA_PUT_U8(msg, IFLA_OPERSTATE, link->l_operstate);

	if (link->ce_mask & LINK_ATTR_LINKMODE)
		NLA_PUT_U8(msg, IFLA_LINKMODE, link->l_linkmode);

	if (link->ce_mask & LINK_ATTR_IFALIAS)
		NLA_PUT_STRING(msg, IFLA_IFALIAS, link->l_ifalias);

	if (link->ce_mask & LINK_ATTR_LINK)
		NLA_PUT_U32(msg, IFLA_LINK, link->l_link);

	if ((link->ce_mask & LINK_ATTR_LINKINFO) && link->l_info_ops) {
		struct nlattr *info;

		if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
			goto nla_put_failure;

		NLA_PUT_STRING(msg, IFLA_INFO_KIND, link->l_info_ops->io_name);

		if (link->l_info_ops->io_put_attrs &&
		    link->l_info_ops->io_put_attrs(msg, link) < 0)
			goto nla_put_failure;

		nla_nest_end(msg, info);
	}

	if (!(af_spec = nla_nest_start(msg, IFLA_AF_SPEC)))
		goto nla_put_failure;

	if (do_foreach_af(link, af_fill, msg) < 0)
		goto nla_put_failure;

	nla_nest_end(msg, af_spec);

	*result = msg;
	return 0;

nla_put_failure:
	nlmsg_free(msg);
	return -NLE_MSGSIZE;
}

/**
 * Build a netlink message requesting the addition of a new virtual link
 * @arg link		new link to add
 * @arg flags		additional netlink message flags
 * @arg result		pointer to store resulting netlink message
 *
 * The behaviour of this function is identical to rtnl_link_add() with
 * the exception that it will not send the message but return it in the
 * provided return pointer instead.
 *
 * @see rtnl_link_add()
 *
 * @note This operation is not supported on all kernel versions.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_build_add_request(struct rtnl_link *link, int flags,
				struct nl_msg **result)
{
	struct ifinfomsg ifi = {
		.ifi_family = link->l_family,
		.ifi_index = link->l_index,
		.ifi_flags = link->l_flags,
	};

	return build_link_msg(RTM_NEWLINK, &ifi, link, flags, result);
}

/**
 * Add virtual link
 * @arg sk		netlink socket.
 * @arg link		new link to add
 * @arg flags		additional netlink message flags
 *
 * Builds a \c RTM_NEWLINK netlink message requesting the addition of
 * a new virtual link.
 *
 * After sending, the function will wait for the ACK or an eventual
 * error message to be received and will therefore block until the
 * operation has been completed.
 *
 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
 *       this function to return immediately after sending. In this case,
 *       it is the responsibility of the caller to handle any error
 *       messages returned.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_add(struct nl_sock *sk, struct rtnl_link *link, int flags)
{
	struct nl_msg *msg;
	int err;
	
	err = rtnl_link_build_add_request(link, flags, &msg);
	if (err < 0)
		return err;

	err = nl_send_auto_complete(sk, msg);
	nlmsg_free(msg);
	if (err < 0)
		return err;

	return wait_for_ack(sk);
}

/**
 * Build a netlink message requesting the modification of a link
 * @arg orig		original link to change
 * @arg changes		link containing the changes to be made
 * @arg flags		additional netlink message flags
 * @arg result		pointer to store resulting netlink message
 *
 * The behaviour of this function is identical to rtnl_link_change() with
 * the exception that it will not send the message but return it in the
 * provided return pointer instead.
 *
 * @see rtnl_link_change()
 *
 * @note The resulting message will have message type set to RTM_NEWLINK
 *       which may not work with older kernels. You may have to modify it
 *       to RTM_SETLINK (does not allow changing link info attributes) to
 *       have the change request work with older kernels.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_build_change_request(struct rtnl_link *orig,
				   struct rtnl_link *changes, int flags,
				   struct nl_msg **result)
{
	struct ifinfomsg ifi = {
		.ifi_family = orig->l_family,
		.ifi_index = orig->l_index,
	};
	int err;

	if (changes->ce_mask & LINK_ATTR_FLAGS) {
		ifi.ifi_flags = orig->l_flags & ~changes->l_flag_mask;
		ifi.ifi_flags |= changes->l_flags;
	}

	if (changes->l_family && changes->l_family != orig->l_family) {
		APPBUG("link change: family is immutable");
		return -NLE_IMMUTABLE;
	}

	/* Avoid unnecessary name change requests */
	if (orig->ce_mask & LINK_ATTR_IFINDEX &&
	    orig->ce_mask & LINK_ATTR_IFNAME &&
	    changes->ce_mask & LINK_ATTR_IFNAME &&
	    !strcmp(orig->l_name, changes->l_name))
		changes->ce_mask &= ~LINK_ATTR_IFNAME;

	if ((err = build_link_msg(RTM_NEWLINK, &ifi, changes, flags, result)) < 0)
		goto errout;

	return 0;

errout:
	return err;
}

/**
 * Change link
 * @arg sk		netlink socket.
 * @arg orig		original link to be changed
 * @arg changes		link containing the changes to be made
 * @arg flags		additional netlink message flags
 *
 * Builds a \c RTM_NEWLINK netlink message requesting the change of
 * a network link. If -EOPNOTSUPP is returned by the kernel, the
 * message type will be changed to \c RTM_SETLINK and the message is
 * resent to work around older kernel versions.
 *
 * The link to be changed is looked up based on the interface index
 * supplied in the \p orig link. Optionaly the link name is used but
 * only if no interface index is provided, otherwise providing an
 * link name will result in the link name being changed.
 *
 * If no matching link exists, the function will return
 * -NLE_OBJ_NOTFOUND.
 *
 * After sending, the function will wait for the ACK or an eventual
 * error message to be received and will therefore block until the
 * operation has been completed.
 *
 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
 *       this function to return immediately after sending. In this case,
 *       it is the responsibility of the caller to handle any error
 *       messages returned.
 *
 * @note The link name can only be changed if the link has been put
 *       in opertional down state. (~IF_UP)
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_change(struct nl_sock *sk, struct rtnl_link *orig,
		     struct rtnl_link *changes, int flags)
{
	struct nl_msg *msg;
	int err;
	
	err = rtnl_link_build_change_request(orig, changes, flags, &msg);
	if (err < 0)
		return err;

retry:
	err = nl_send_auto_complete(sk, msg);
	if (err < 0)
		goto errout;

	err = wait_for_ack(sk);
	if (err == -NLE_OPNOTSUPP && msg->nm_nlh->nlmsg_type == RTM_NEWLINK) {
		msg->nm_nlh->nlmsg_type = RTM_SETLINK;
		goto retry;
	}

errout:
	nlmsg_free(msg);
	return err;
}

/**
 * Build a netlink message requesting the deletion of a link
 * @arg link		Link to delete
 * @arg result		Pointer to store resulting netlink message
 *
 * The behaviour of this function is identical to rtnl_link_delete() with
 * the exception that it will not send the message but return it in the
 * provided return pointer instead.
 *
 * @see rtnl_link_delete()
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_build_delete_request(const struct rtnl_link *link,
				   struct nl_msg **result)
{
	struct nl_msg *msg;
	struct ifinfomsg ifi = {
		.ifi_index = link->l_index,
	};

	if (!(link->ce_mask & (LINK_ATTR_IFINDEX | LINK_ATTR_IFNAME))) {
		APPBUG("ifindex or name must be specified");
		return -NLE_MISSING_ATTR;
	}

	if (!(msg = nlmsg_alloc_simple(RTM_DELLINK, 0)))
		return -NLE_NOMEM;

	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
		goto nla_put_failure;

	if (link->ce_mask & LINK_ATTR_IFNAME)
		NLA_PUT_STRING(msg, IFLA_IFNAME, link->l_name);

	*result = msg;
	return 0;

nla_put_failure:
	nlmsg_free(msg);
	return -NLE_MSGSIZE;
}

/**
 * Delete link
 * @arg sk		Netlink socket
 * @arg link		Link to delete
 *
 * Builds a \c RTM_DELLINK netlink message requesting the deletion of
 * a network link which has been previously added to the kernel and
 * sends the message to the kernel.
 *
 * If no matching link exists, the function will return
 * -NLE_OBJ_NOTFOUND.
 *
 * After sending, the function will wait for the ACK or an eventual
 * error message to be received and will therefore block until the
 * operation has been completed.
 *
 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
 *       this function to return immediately after sending. In this case,
 *       it is the responsibility of the caller to handle any error
 *       messages returned.
 *
 * @note Only virtual links such as dummy interface or vlan interfaces
 *       can be deleted. It is not possible to delete physical interfaces
 *       such as ethernet interfaces or the loopback device.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_delete(struct nl_sock *sk, const struct rtnl_link *link)
{
	struct nl_msg *msg;
	int err;
	
	if ((err = rtnl_link_build_delete_request(link, &msg)) < 0)
		return err;

	return nl_send_sync(sk, msg);
}


/**
 * Build a netlink message requesting a link
 * @arg ifindex		Interface index
 * @arg name		Name of link
 * @arg result		Pointer to store resulting netlink message
 *
 * The behaviour of this function is identical to rtnl_link_get_kernel()
 * with the exception that it will not send the message but return it in
 * the provided return pointer instead.
 *
 * @see rtnl_link_get_kernel()
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_build_get_request(int ifindex, const char *name,
				struct nl_msg **result)
{
	struct ifinfomsg ifi;
	struct nl_msg *msg;

	if (ifindex <= 0 && !name) {
		APPBUG("ifindex or name must be specified");
		return -NLE_MISSING_ATTR;
	}

	memset(&ifi, 0, sizeof(ifi));

	if (!(msg = nlmsg_alloc_simple(RTM_GETLINK, 0)))
		return -NLE_NOMEM;

	if (ifindex > 0)
		ifi.ifi_index = ifindex;

	if (nlmsg_append(msg, &ifi, sizeof(ifi), NLMSG_ALIGNTO) < 0)
		goto nla_put_failure;

	if (name)
		NLA_PUT_STRING(msg, IFLA_IFNAME, name);

	*result = msg;
	return 0;

nla_put_failure:
	nlmsg_free(msg);
	return -NLE_MSGSIZE;
}

/**
 * Get a link object directly from the kernel
 * @arg sk		Netlink socket
 * @arg ifindex		Interface index
 * @arg name		name of link
 * @arg result		result pointer to return link object
 *
 * This function builds a \c RTM_GETLINK netlink message to request
 * a specific link directly from the kernel. The returned answer is
 * parsed into a struct rtnl_link object and returned via the result
 * pointer or -NLE_OBJ_NOTFOUND is returned if no matching link was
 * found.
 *
 * @note Disabling auto-ack (nl_socket_disable_auto_ack()) will cause
 *       this function to return immediately after sending. In this case,
 *       it is the responsibility of the caller to handle any error
 *       messages returned.
 *
 * @return 0 on success or a negative error code.
 */
int rtnl_link_get_kernel(struct nl_sock *sk, int ifindex, const char *name,
			 struct rtnl_link **result)
{
	struct nl_msg *msg = NULL;
	struct nl_object *obj;
	int err;

	if ((err = rtnl_link_build_get_request(ifindex, name, &msg)) < 0)
		return err;

	err = nl_send_auto(sk, msg);
	nlmsg_free(msg);
	if (err < 0)
		return err;

	if ((err = nl_pickup(sk, link_msg_parser, &obj)) < 0)
		return err;

	/* We have used link_msg_parser(), object is definitely a link */
	*result = (struct rtnl_link *) obj;

	return 0;
}

/** @} */

/**
 * @name Name <-> Index Translations
 * @{
 */

/**
 * Translate an interface index to the corresponding link name
 * @arg cache		link cache
 * @arg ifindex		link interface index
 * @arg dst		destination buffer
 * @arg len		length of destination buffer
 *
 * Translates the specified interface index to the corresponding
 * link name and stores the name in the destination buffer.
 *
 * @return link name or NULL if no match was found.
 */
char * rtnl_link_i2name(struct nl_cache *cache, int ifindex, char *dst,
			size_t len)
{
	struct rtnl_link *link = rtnl_link_get(cache, ifindex);

	if (link) {
		strncpy(dst, link->l_name, len - 1);
		rtnl_link_put(link);
		return dst;
	}

	return NULL;
}

/**
 * Translate a link name to the corresponding interface index
 * @arg cache		link cache
 * @arg name		link name
 *
 * @return interface index or 0 if no match was found.
 */
int rtnl_link_name2i(struct nl_cache *cache, const char *name)
{
	int ifindex = 0;
	struct rtnl_link *link;
	
	link = rtnl_link_get_by_name(cache, name);
	if (link) {
		ifindex = link->l_index;
		rtnl_link_put(link);
	}

	return ifindex;
}

/** @} */

/**
 * @name Link Flags Translations
 * @{
 */

static const struct trans_tbl link_flags[] = {
	__ADD(IFF_LOOPBACK, loopback)
	__ADD(IFF_BROADCAST, broadcast)
	__ADD(IFF_POINTOPOINT, pointopoint)
	__ADD(IFF_MULTICAST, multicast)
	__ADD(IFF_NOARP, noarp)
	__ADD(IFF_ALLMULTI, allmulti)
	__ADD(IFF_PROMISC, promisc)
	__ADD(IFF_MASTER, master)
	__ADD(IFF_SLAVE, slave)
	__ADD(IFF_DEBUG, debug)
	__ADD(IFF_DYNAMIC, dynamic)
	__ADD(IFF_AUTOMEDIA, automedia)
	__ADD(IFF_PORTSEL, portsel)
	__ADD(IFF_NOTRAILERS, notrailers)
	__ADD(IFF_UP, up)
	__ADD(IFF_RUNNING, running)
	__ADD(IFF_LOWER_UP, lowerup)
	__ADD(IFF_DORMANT, dormant)
	__ADD(IFF_ECHO, echo)
};

char * rtnl_link_flags2str(int flags, char *buf, size_t len)
{
	return __flags2str(flags, buf, len, link_flags,
			   ARRAY_SIZE(link_flags));
}

int rtnl_link_str2flags(const char *name)
{
	return __str2flags(name, link_flags, ARRAY_SIZE(link_flags));
}

/** @} */

/**
 * @name Link Statistics Translations
 * @{
 */

static const struct trans_tbl link_stats[] = {
	__ADD(RTNL_LINK_RX_PACKETS, rx_packets)
	__ADD(RTNL_LINK_TX_PACKETS, tx_packets)
	__ADD(RTNL_LINK_RX_BYTES, rx_bytes)
	__ADD(RTNL_LINK_TX_BYTES, tx_bytes)
	__ADD(RTNL_LINK_RX_ERRORS, rx_errors)
	__ADD(RTNL_LINK_TX_ERRORS, tx_errors)
	__ADD(RTNL_LINK_RX_DROPPED, rx_dropped)
	__ADD(RTNL_LINK_TX_DROPPED, tx_dropped)
	__ADD(RTNL_LINK_RX_COMPRESSED, rx_compressed)
	__ADD(RTNL_LINK_TX_COMPRESSED, tx_compressed)
	__ADD(RTNL_LINK_RX_FIFO_ERR, rx_fifo_err)
	__ADD(RTNL_LINK_TX_FIFO_ERR, tx_fifo_err)
	__ADD(RTNL_LINK_RX_LEN_ERR, rx_len_err)
	__ADD(RTNL_LINK_RX_OVER_ERR, rx_over_err)
	__ADD(RTNL_LINK_RX_CRC_ERR, rx_crc_err)
	__ADD(RTNL_LINK_RX_FRAME_ERR, rx_frame_err)
	__ADD(RTNL_LINK_RX_MISSED_ERR, rx_missed_err)
	__ADD(RTNL_LINK_TX_ABORT_ERR, tx_abort_err)
	__ADD(RTNL_LINK_TX_CARRIER_ERR, tx_carrier_err)
	__ADD(RTNL_LINK_TX_HBEAT_ERR, tx_hbeat_err)
	__ADD(RTNL_LINK_TX_WIN_ERR, tx_win_err)
	__ADD(RTNL_LINK_COLLISIONS, collisions)
	__ADD(RTNL_LINK_MULTICAST, multicast)
	__ADD(RTNL_LINK_IP6_INPKTS, Ip6InReceives)
	__ADD(RTNL_LINK_IP6_INHDRERRORS, Ip6InHdrErrors)
	__ADD(RTNL_LINK_IP6_INTOOBIGERRORS, Ip6InTooBigErrors)
	__ADD(RTNL_LINK_IP6_INNOROUTES, Ip6InNoRoutes)
	__ADD(RTNL_LINK_IP6_INADDRERRORS, Ip6InAddrErrors)
	__ADD(RTNL_LINK_IP6_INUNKNOWNPROTOS, Ip6InUnknownProtos)
	__ADD(RTNL_LINK_IP6_INTRUNCATEDPKTS, Ip6InTruncatedPkts)
	__ADD(RTNL_LINK_IP6_INDISCARDS, Ip6InDiscards)
	__ADD(RTNL_LINK_IP6_INDELIVERS, Ip6InDelivers)
	__ADD(RTNL_LINK_IP6_OUTFORWDATAGRAMS, Ip6OutForwDatagrams)
	__ADD(RTNL_LINK_IP6_OUTPKTS, Ip6OutRequests)
	__ADD(RTNL_LINK_IP6_OUTDISCARDS, Ip6OutDiscards)
	__ADD(RTNL_LINK_IP6_OUTNOROUTES, Ip6OutNoRoutes)
	__ADD(RTNL_LINK_IP6_REASMTIMEOUT, Ip6ReasmTimeout)
	__ADD(RTNL_LINK_IP6_REASMREQDS, Ip6ReasmReqds)
	__ADD(RTNL_LINK_IP6_REASMOKS, Ip6ReasmOKs)
	__ADD(RTNL_LINK_IP6_REASMFAILS, Ip6ReasmFails)
	__ADD(RTNL_LINK_IP6_FRAGOKS, Ip6FragOKs)
	__ADD(RTNL_LINK_IP6_FRAGFAILS, Ip6FragFails)
	__ADD(RTNL_LINK_IP6_FRAGCREATES, Ip6FragCreates)
	__ADD(RTNL_LINK_IP6_INMCASTPKTS, Ip6InMcastPkts)
	__ADD(RTNL_LINK_IP6_OUTMCASTPKTS, Ip6OutMcastPkts)
	__ADD(RTNL_LINK_IP6_INBCASTPKTS, Ip6InBcastPkts)
	__ADD(RTNL_LINK_IP6_OUTBCASTPKTS, Ip6OutBcastPkts)
	__ADD(RTNL_LINK_IP6_INOCTETS, Ip6InOctets)
	__ADD(RTNL_LINK_IP6_OUTOCTETS, Ip6OutOctets)
	__ADD(RTNL_LINK_IP6_INMCASTOCTETS, Ip6InMcastOctets)
	__ADD(RTNL_LINK_IP6_OUTMCASTOCTETS, Ip6OutMcastOctets)
	__ADD(RTNL_LINK_IP6_INBCASTOCTETS, Ip6InBcastOctets)
	__ADD(RTNL_LINK_IP6_OUTBCASTOCTETS, Ip6OutBcastOctets)
	__ADD(RTNL_LINK_ICMP6_INMSGS, ICMP6_InMsgs)
	__ADD(RTNL_LINK_ICMP6_INERRORS, ICMP6_InErrors)
	__ADD(RTNL_LINK_ICMP6_OUTMSGS, ICMP6_OutMsgs)
	__ADD(RTNL_LINK_ICMP6_OUTERRORS, ICMP6_OutErrors)
};

char *rtnl_link_stat2str(int st, char *buf, size_t len)
{
	return __type2str(st, buf, len, link_stats, ARRAY_SIZE(link_stats));
}

int rtnl_link_str2stat(const char *name)
{
	return __str2type(name, link_stats, ARRAY_SIZE(link_stats));
}

/** @} */

/**
 * @name Link Operstate Translations
 * @{
 */

static const struct trans_tbl link_operstates[] = {
	__ADD(IF_OPER_UNKNOWN, unknown)
	__ADD(IF_OPER_NOTPRESENT, notpresent)
	__ADD(IF_OPER_DOWN, down)
	__ADD(IF_OPER_LOWERLAYERDOWN, lowerlayerdown)
	__ADD(IF_OPER_TESTING, testing)
	__ADD(IF_OPER_DORMANT, dormant)
	__ADD(IF_OPER_UP, up)
};

char *rtnl_link_operstate2str(uint8_t st, char *buf, size_t len)
{
	return __type2str(st, buf, len, link_operstates,
			  ARRAY_SIZE(link_operstates));
}

int rtnl_link_str2operstate(const char *name)
{
	return __str2type(name, link_operstates,
			  ARRAY_SIZE(link_operstates));
}

/** @} */

/**
 * @name Link Mode Translations
 * @{
 */

static const struct trans_tbl link_modes[] = {
	__ADD(IF_LINK_MODE_DEFAULT, default)
	__ADD(IF_LINK_MODE_DORMANT, dormant)
};

char *rtnl_link_mode2str(uint8_t st, char *buf, size_t len)
{
	return __type2str(st, buf, len, link_modes, ARRAY_SIZE(link_modes));
}

int rtnl_link_str2mode(const char *name)
{
	return __str2type(name, link_modes, ARRAY_SIZE(link_modes));
}

/** @} */

/**
 * @name Attributes
 * @{
 */

void rtnl_link_set_qdisc(struct rtnl_link *link, const char *qdisc)
{
	strncpy(link->l_qdisc, qdisc, sizeof(link->l_qdisc) - 1);
	link->ce_mask |= LINK_ATTR_QDISC;
}

char *rtnl_link_get_qdisc(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_QDISC)
		return link->l_qdisc;
	else
		return NULL;
}

void rtnl_link_set_name(struct rtnl_link *link, const char *name)
{
	strncpy(link->l_name, name, sizeof(link->l_name) - 1);
	link->ce_mask |= LINK_ATTR_IFNAME;
}

char *rtnl_link_get_name(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_IFNAME)
		return link->l_name;
	else
		return NULL;
}

static inline void __assign_addr(struct rtnl_link *link, struct nl_addr **pos,
				 struct nl_addr *new, int flag)
{
	if (*pos)
		nl_addr_put(*pos);

	nl_addr_get(new);
	*pos = new;

	link->ce_mask |= flag;
}

void rtnl_link_set_addr(struct rtnl_link *link, struct nl_addr *addr)
{
	__assign_addr(link, &link->l_addr, addr, LINK_ATTR_ADDR);
}

struct nl_addr *rtnl_link_get_addr(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_ADDR)
		return link->l_addr;
	else
		return NULL;
}

void rtnl_link_set_broadcast(struct rtnl_link *link, struct nl_addr *brd)
{
	__assign_addr(link, &link->l_bcast, brd, LINK_ATTR_BRD);
}

struct nl_addr *rtnl_link_get_broadcast(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_BRD)
		return link->l_bcast;
	else
		return NULL;
}

void rtnl_link_set_flags(struct rtnl_link *link, unsigned int flags)
{
	link->l_flag_mask |= flags;
	link->l_flags |= flags;
	link->ce_mask |= LINK_ATTR_FLAGS;
}

void rtnl_link_unset_flags(struct rtnl_link *link, unsigned int flags)
{
	link->l_flag_mask |= flags;
	link->l_flags &= ~flags;
	link->ce_mask |= LINK_ATTR_FLAGS;
}

unsigned int rtnl_link_get_flags(struct rtnl_link *link)
{
	return link->l_flags;
}

void rtnl_link_set_family(struct rtnl_link *link, int family)
{
	link->l_family = family;
	link->ce_mask |= LINK_ATTR_FAMILY;
}

int rtnl_link_get_family(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_FAMILY)
		return link->l_family;
	else
		return AF_UNSPEC;
}

void rtnl_link_set_arptype(struct rtnl_link *link, unsigned int arptype)
{
	link->l_arptype = arptype;
}

unsigned int rtnl_link_get_arptype(struct rtnl_link *link)
{
	return link->l_arptype;
}

void rtnl_link_set_ifindex(struct rtnl_link *link, int ifindex)
{
	link->l_index = ifindex;
	link->ce_mask |= LINK_ATTR_IFINDEX;
}

int rtnl_link_get_ifindex(struct rtnl_link *link)
{
	return link->l_index;
}

void rtnl_link_set_mtu(struct rtnl_link *link, unsigned int mtu)
{
	link->l_mtu = mtu;
	link->ce_mask |= LINK_ATTR_MTU;
}

unsigned int rtnl_link_get_mtu(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_MTU)
		return link->l_mtu;
	else
		return 0;
}

void rtnl_link_set_txqlen(struct rtnl_link *link, unsigned int txqlen)
{
	link->l_txqlen = txqlen;
	link->ce_mask |= LINK_ATTR_TXQLEN;
}

unsigned int rtnl_link_get_txqlen(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_TXQLEN)
		return link->l_txqlen;
	else
		return UINT_MAX;
}

void rtnl_link_set_weight(struct rtnl_link *link, unsigned int weight)
{
	link->l_weight = weight;
	link->ce_mask |= LINK_ATTR_WEIGHT;
}

unsigned int rtnl_link_get_weight(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_WEIGHT)
		return link->l_weight;
	else
		return UINT_MAX;
}

void rtnl_link_set_link(struct rtnl_link *link, int ifindex)
{
	link->l_link = ifindex;
	link->ce_mask |= LINK_ATTR_LINK;
}

int rtnl_link_get_link(struct rtnl_link *link)
{
	return link->l_link;
}

void rtnl_link_set_master(struct rtnl_link *link, int ifindex)
{
	link->l_master = ifindex;
	link->ce_mask |= LINK_ATTR_MASTER;
}

int rtnl_link_get_master(struct rtnl_link *link)
{
	return link->l_master;
}

void rtnl_link_set_operstate(struct rtnl_link *link, uint8_t operstate)
{
	link->l_operstate = operstate;
	link->ce_mask |= LINK_ATTR_OPERSTATE;
}

uint8_t rtnl_link_get_operstate(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_OPERSTATE)
		return link->l_operstate;
	else
		return IF_OPER_UNKNOWN;
}

void rtnl_link_set_linkmode(struct rtnl_link *link, uint8_t linkmode)
{
	link->l_linkmode = linkmode;
	link->ce_mask |= LINK_ATTR_LINKMODE;
}

uint8_t rtnl_link_get_linkmode(struct rtnl_link *link)
{
	if (link->ce_mask & LINK_ATTR_LINKMODE)
		return link->l_linkmode;
	else
		return IF_LINK_MODE_DEFAULT;
}

/**
 * Return alias name of link (SNMP IfAlias)
 * @arg link		Link object
 *
 * @return Alias name or NULL if not set.
 */
const char *rtnl_link_get_ifalias(struct rtnl_link *link)
{
	return link->l_ifalias;
}

/**
 * Set alias name of link (SNMP IfAlias)
 * @arg link		Link object
 * @arg alias		Alias name or NULL to unset
 *
 * Sets the alias name of the link to the specified name. The alias
 * name can be unset by specyfing NULL as the alias. The name will
 * be strdup()ed, so no need to provide a persistent character string.
 */
void rtnl_link_set_ifalias(struct rtnl_link *link, const char *alias)
{
	free(link->l_ifalias);
	link->ce_mask &= ~LINK_ATTR_IFALIAS;

	if (alias) {
		link->l_ifalias = strdup(alias);
		link->ce_mask |= LINK_ATTR_IFALIAS;
	}
}

/**
 * Retrieve number of PCI VFs of link
 * @arg link		Link object
 * @arg num_vf		Pointer to store number of VFs
 *
 * @return 0 if value is available or -NLE_OPNOTSUPP if not.
 */
int rtnl_link_get_num_vf(struct rtnl_link *link, uint32_t *num_vf)
{
	if (link->ce_mask & LINK_ATTR_NUM_VF) {
		*num_vf = link->l_num_vf;
		return 0;
	} else
		return -NLE_OPNOTSUPP;
}

uint64_t rtnl_link_get_stat(struct rtnl_link *link, int id)
{
	if (id < 0 || id > RTNL_LINK_STATS_MAX)
		return 0;

	return link->l_stats[id];
}

/**
 * Set value of a link statistics counter
 * @arg link		Link object
 * @arg id		Counter ID
 * @arg value		New value
 *
 * @return 0 on success or a negative error code
 */
int rtnl_link_set_stat(struct rtnl_link *link, const unsigned int id,
		       const uint64_t value)
{
	if (id > RTNL_LINK_STATS_MAX)
		return -NLE_INVAL;

	link->l_stats[id] = value;

	return 0;
}

/**
 * Specify the info type of a link
 * @arg link	link object
 * @arg type	info type
 *
 * Looks up the info type and prepares the link to store info type
 * specific attributes. If an info type has been assigned already
 * it will be released with all changes lost.
 *
 * @return 0 on success or a negative errror code.
 */
int rtnl_link_set_info_type(struct rtnl_link *link, const char *type)
{
	struct rtnl_link_info_ops *io;
	int err;

	if ((io = rtnl_link_info_ops_lookup(type)) == NULL)
		return -NLE_OPNOTSUPP;

	if (link->l_info_ops)
		release_link_info(link);

	if (io->io_alloc && (err = io->io_alloc(link)) < 0)
		return err;

	link->ce_mask |= LINK_ATTR_LINKINFO;
	link->l_info_ops = io;

	return 0;
}

/**
 * Return info type of a link
 * @arg link	link object
 *
 * @note The returned pointer is only valid as long as the link exists
 * @return Info type name or NULL if unknown.
 */
char *rtnl_link_get_info_type(struct rtnl_link *link)
{
	if (link->l_info_ops)
		return link->l_info_ops->io_name;
	else
		return NULL;
}

/** @} */

static struct nl_object_ops link_obj_ops = {
	.oo_name		= "route/link",
	.oo_size		= sizeof(struct rtnl_link),
	.oo_free_data		= link_free_data,
	.oo_clone		= link_clone,
	.oo_dump = {
	    [NL_DUMP_LINE]	= link_dump_line,
	    [NL_DUMP_DETAILS]	= link_dump_details,
	    [NL_DUMP_STATS]	= link_dump_stats,
	},
	.oo_compare		= link_compare,
	.oo_attrs2str		= link_attrs2str,
	.oo_id_attrs		= LINK_ATTR_IFINDEX,
};

static struct nl_af_group link_groups[] = {
	{ AF_UNSPEC,	RTNLGRP_LINK },
	{ END_OF_GROUP_LIST },
};

static struct nl_cache_ops rtnl_link_ops = {
	.co_name		= "route/link",
	.co_hdrsize		= sizeof(struct ifinfomsg),
	.co_msgtypes		= {
					{ RTM_NEWLINK, NL_ACT_NEW, "new" },
					{ RTM_DELLINK, NL_ACT_DEL, "del" },
					{ RTM_GETLINK, NL_ACT_GET, "get" },
					{ RTM_SETLINK, NL_ACT_CHANGE, "set" },
					END_OF_MSGTYPES_LIST,
				  },
	.co_protocol		= NETLINK_ROUTE,
	.co_groups		= link_groups,
	.co_request_update	= link_request_update,
	.co_msg_parser		= link_msg_parser,
	.co_obj_ops		= &link_obj_ops,
};

static void __init link_init(void)
{
	nl_cache_mngt_register(&rtnl_link_ops);
}

static void __exit link_exit(void)
{
	nl_cache_mngt_unregister(&rtnl_link_ops);
}

/** @} */
