]> git.ipfire.org Git - thirdparty/libnl.git/commitdiff
Link info interface and vlan support
authorThomas Graf <tgr@deb.localdomain>
Tue, 8 Jan 2008 14:00:46 +0000 (15:00 +0100)
committerThomas Graf <tgr@deb.localdomain>
Tue, 8 Jan 2008 14:00:46 +0000 (15:00 +0100)
Adds an external interface to implement link info types and
implements the type vlan.

include/linux/if_link.h
include/netlink-types.h
include/netlink/route/link.h
include/netlink/route/link/info-api.h [new file with mode: 0644]
include/netlink/route/link/vlan.h [new file with mode: 0644]
lib/Makefile
lib/route/link.c
lib/route/link/api.c [new file with mode: 0644]
lib/route/link/vlan.c [new file with mode: 0644]

index 604c2434f71c0a0436b63af4f37b6d7f4e14a8b3..84c3492ae5cb510604c50dc4ba09941ac95f7300 100644 (file)
@@ -76,6 +76,9 @@ enum
 #define IFLA_WEIGHT IFLA_WEIGHT
        IFLA_OPERSTATE,
        IFLA_LINKMODE,
+       IFLA_LINKINFO,
+#define IFLA_LINKINFO IFLA_LINKINFO
+       IFLA_NET_NS_PID,
        __IFLA_MAX
 };
 
@@ -140,4 +143,49 @@ struct ifla_cacheinfo
        __u32   retrans_time;
 };
 
+enum
+{
+       IFLA_INFO_UNSPEC,
+       IFLA_INFO_KIND,
+       IFLA_INFO_DATA,
+       IFLA_INFO_XSTATS,
+       __IFLA_INFO_MAX,
+};
+
+#define IFLA_INFO_MAX  (__IFLA_INFO_MAX - 1)
+
+/* VLAN section */
+
+enum
+{
+       IFLA_VLAN_UNSPEC,
+       IFLA_VLAN_ID,
+       IFLA_VLAN_FLAGS,
+       IFLA_VLAN_EGRESS_QOS,
+       IFLA_VLAN_INGRESS_QOS,
+       __IFLA_VLAN_MAX,
+};
+
+#define IFLA_VLAN_MAX  (__IFLA_VLAN_MAX - 1)
+
+struct ifla_vlan_flags {
+       __u32   flags;
+       __u32   mask;
+};
+
+enum
+{
+       IFLA_VLAN_QOS_UNSPEC,
+       IFLA_VLAN_QOS_MAPPING,
+       __IFLA_VLAN_QOS_MAX
+};
+
+#define IFLA_VLAN_QOS_MAX      (__IFLA_VLAN_QOS_MAX - 1)
+
+struct ifla_vlan_qos_mapping
+{
+       __u32 from;
+       __u32 to;
+};
+
 #endif /* _LINUX_IF_LINK_H */
index 5a23450e6effd68f38d4a4a23503d0c31fb6993c..f273da39af9b066ec2148103b4afeac3d34b56b5 100644 (file)
@@ -175,6 +175,9 @@ struct rtnl_link
        uint32_t        l_flag_mask;
        uint8_t         l_operstate;
        uint8_t         l_linkmode;
+       /* 2 byte hole */
+       struct rtnl_link_info_ops *l_info_ops;
+       void *          l_info;
 };
 
 struct rtnl_ncacheinfo
index 78f2b0be41b0c97ffe14b7c085554b19f3158a9c..caaa7928a602e7530d9dec051ad6152670ee3a52 100644 (file)
@@ -158,6 +158,10 @@ extern uint8_t                     rtnl_link_get_linkmode(struct rtnl_link *);
 
 extern uint64_t                rtnl_link_get_stat(struct rtnl_link *, int);
 
+extern int                     rtnl_link_set_info_type(struct rtnl_link *,
+                                                       const char *);
+extern char *                  rtnl_link_get_info_type(struct rtnl_link *);
+
 #ifdef __cplusplus
 }
 #endif
diff --git a/include/netlink/route/link/info-api.h b/include/netlink/route/link/info-api.h
new file mode 100644 (file)
index 0000000..2ccce9d
--- /dev/null
@@ -0,0 +1,71 @@
+/*
+ * netlink/route/link/info-api.h       Link Info API
+ *
+ *     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-2008 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_INFO_API_H_
+#define NETLINK_LINK_INFO_API_H_
+
+#include <netlink/netlink.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @ingroup link_info
+ *
+ * Link info operations
+ */
+struct rtnl_link_info_ops
+{
+       /** Name of operations, must match name on kernel side */
+       char *          io_name;
+
+       /** Reference count (internal, do not use) */
+       int             io_refcnt;
+
+       /** Called to assign an info type to a link.
+        * Has to allocate enough resources to hold attributes. Can
+        * use link->l_info to store a pointer. */
+       int           (*io_alloc)(struct rtnl_link *);
+
+       /** Called to parse the link info attribute.
+        * Must parse the attribute and assign all values to the link.
+        */
+       int           (*io_parse)(struct rtnl_link *,
+                                 struct nlattr *,
+                                 struct nlattr *);
+
+       /** Called when the link object is dumped.
+        * Must dump the info type specific attributes. */
+       int           (*io_dump[NL_DUMP_MAX+1])(struct rtnl_link *,
+                                               struct nl_dump_params *, int);
+
+       /** Called when a link object is cloned.
+        * Must clone all info type specific attributes. */
+       int           (*io_clone)(struct rtnl_link *, struct rtnl_link *);
+
+       /** Called when construction a link netlink message.
+        * Must append all info type specific attributes to the message. */
+       int           (*io_put_attrs)(struct nl_msg *, struct rtnl_link *);
+
+       /** Called to release all resources previously allocated
+        * in either io_alloc() or io_parse(). */
+       void          (*io_free)(struct rtnl_link *);
+
+       struct rtnl_link_info_ops *     io_next;
+};
+
+extern struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *);
+
+extern int                     rtnl_link_register_info(struct rtnl_link_info_ops *);
+extern int                     rtnl_link_unregister_info(struct rtnl_link_info_ops *);
+
+#endif
diff --git a/include/netlink/route/link/vlan.h b/include/netlink/route/link/vlan.h
new file mode 100644 (file)
index 0000000..a3ad76d
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * netlink/route/link/vlan.h           VLAN interface
+ *
+ *     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-2008 Thomas Graf <tgraf@suug.ch>
+ */
+
+#ifndef NETLINK_LINK_VLAN_H_
+#define NETLINK_LINK_VLAN_H_
+
+#include <netlink/netlink.h>
+#include <netlink/route/link.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct vlan_map
+{
+       uint32_t                vm_from;
+       uint32_t                vm_to;
+};
+
+#define VLAN_PRIO_MAX 7
+
+extern char *          rtnl_link_vlan_flags2str(int, char *, size_t);
+extern int             rtnl_link_vlan_str2flags(const char *);
+
+extern int             rtnl_link_vlan_set_id(struct rtnl_link *, int);
+extern int             rtnl_link_vlan_get_id(struct rtnl_link *);
+
+extern int             rtnl_link_vlan_set_flags(struct rtnl_link *,
+                                                unsigned int);
+extern int             rtnl_link_vlan_unset_flags(struct rtnl_link *,
+                                                  unsigned int);
+extern unsigned int    rtnl_link_vlan_get_flags(struct rtnl_link *);
+
+extern int             rtnl_link_vlan_set_ingress_map(struct rtnl_link *,
+                                                      int, uint32_t);
+extern uint32_t *      rtnl_link_vlan_get_ingress_map(struct rtnl_link *);
+
+extern int             rtnl_link_vlan_set_egress_map(struct rtnl_link *,
+                                                     uint32_t, int);
+extern struct vlan_map *rtnl_link_vlan_get_egress_map(struct rtnl_link *,
+                                                     int *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
index a65424295b9af5c041c679542d13d962fe018b2d..0bf8af722d89bd1b225a46630942ae5ef60b953c 100644 (file)
@@ -21,6 +21,8 @@ CIN      += $(wildcard route/*.c)
 CIN      += $(wildcard route/sch/*.c)
 # Classifiers
 CIN      += $(wildcard route/cls/*.c)
+# Link Info Modules
+CIN      += $(wildcard route/link/*.c)
 # NETLINK_GENERIC
 CIN      += $(wildcard genl/*.c)
 # fib lookup
index 2fec334ae99575f30d052ea4ba1c6054c6ca8dfa..ab89c2424c57ebd8880ea57a9623aacd00431e27 100644 (file)
  * // 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/object.h>
 #include <netlink/route/rtnl.h>
 #include <netlink/route/link.h>
+#include <netlink/route/link/info-api.h>
 
 /** @cond SKIP */
 #define LINK_ATTR_MTU     0x0001
 #define LINK_ATTR_CHANGE  0x8000
 #define LINK_ATTR_OPERSTATE 0x10000
 #define LINK_ATTR_LINKMODE  0x20000
+#define LINK_ATTR_LINKINFO  0x40000
 
 static struct nl_cache_ops rtnl_link_ops;
 static struct nl_object_ops link_obj_ops;
 /** @endcond */
 
+static void release_link_info(struct rtnl_link *link)
+{
+       struct rtnl_link_info_ops *io = link->l_info_ops;
+
+       if (io != NULL) {
+               io->io_refcnt--;
+               io->io_free(link);
+               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);
        }
@@ -172,6 +211,7 @@ 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)))
@@ -181,6 +221,12 @@ static int link_clone(struct nl_object *_dst, struct nl_object *_src)
                if (!(dst->l_bcast = nl_addr_clone(src->l_bcast)))
                        goto errout;
 
+       if (src->l_info_ops && src->l_info_ops->io_clone) {
+               err = src->l_info_ops->io_clone(dst, src);
+               if (err < 0)
+                       goto errout;
+       }
+
        return 0;
 errout:
        return nl_get_errno();
@@ -196,12 +242,19 @@ static struct nla_policy link_policy[IFLA_MAX+1] = {
        [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_MAP]      = { .minlen = sizeof(struct rtnl_link_ifmap) },
 };
 
+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)
 {
@@ -337,6 +390,34 @@ static int link_msg_parser(struct nl_cache_ops *ops, struct sockaddr_nl *who,
                link->ce_mask |= LINK_ATTR_LINKMODE;
        }
 
+       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] &&
+                   (li[IFLA_INFO_DATA] || li[IFLA_INFO_XSTATS])) {
+                       struct rtnl_link_info_ops *ops;
+                       char *kind;
+
+                       kind = nla_get_string(li[IFLA_INFO_KIND]);
+                       ops = rtnl_link_info_ops_lookup(kind);
+                       if (ops != NULL) {
+                               ops->io_refcnt++;
+                               link->l_info_ops = ops;
+                               err = ops->io_parse(link, li[IFLA_INFO_DATA],
+                                                   li[IFLA_INFO_XSTATS]);
+                               if (err < 0)
+                                       goto errout;
+                       } else {
+                               /* XXX: Warn about unparsed info? */
+                       }
+               }
+       }
+
        err = pp->pp_cb((struct nl_object *) link, pp);
        if (err < 0)
                goto errout;
@@ -360,16 +441,8 @@ static int link_dump_brief(struct nl_object *obj, struct nl_dump_params *p)
        struct rtnl_link *link = (struct rtnl_link *) obj;
        int line = 1;
 
-       dp_dump(p, "%s ", link->l_name);
-
-       if (link->ce_mask & LINK_ATTR_LINK) {
-               struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
-               dp_dump(p, "@%s", ll ? ll->l_name : "NONE");
-               if (ll)
-                       rtnl_link_put(ll);
-       }
-
-       dp_dump(p, "%s ", nl_llproto2str(link->l_arptype, buf, sizeof(buf)));
+       dp_dump(p, "%s %s ", link->l_name,
+                            nl_llproto2str(link->l_arptype, buf, sizeof(buf)));
 
        if (link->l_addr && !nl_addr_iszero(link->l_addr))
                dp_dump(p, "%s ", nl_addr2str(link->l_addr, buf, sizeof(buf)));
@@ -383,7 +456,17 @@ static int link_dump_brief(struct nl_object *obj, struct nl_dump_params *p)
 
        rtnl_link_flags2str(link->l_flags, buf, sizeof(buf));
        if (buf[0])
-               dp_dump(p, "<%s>", buf);
+               dp_dump(p, "<%s> ", buf);
+
+       if (link->ce_mask & LINK_ATTR_LINK) {
+               struct rtnl_link *ll = rtnl_link_get(cache, link->l_link);
+               dp_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_BRIEF])
+               line = link->l_info_ops->io_dump[NL_DUMP_BRIEF](link, p, line);
 
        dp_dump(p, "\n");
 
@@ -430,6 +513,9 @@ static int link_dump_full(struct nl_object *obj, struct nl_dump_params *p)
        dp_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_FULL])
+               line = link->l_info_ops->io_dump[NL_DUMP_FULL](link, p, line);
+
        return line;
 }
 
@@ -495,6 +581,9 @@ static int link_dump_stats(struct nl_object *obj, struct nl_dump_params *p)
                link->l_stats[RTNL_LINK_TX_WIN_ERR],
                link->l_stats[RTNL_LINK_TX_COLLISIONS]);
 
+       if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_STATS])
+               line = link->l_info_ops->io_dump[NL_DUMP_STATS](link, p, line);
+
        return line;
 }
 
@@ -555,6 +644,12 @@ static int link_dump_xml(struct nl_object *obj, struct nl_dump_params *p)
                dp_dump_line(p, line++, "  </stats>\n");
        }
 
+       if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_XML]) {
+               dp_dump_line(p, line++, "  <info>\n");
+               line = link->l_info_ops->io_dump[NL_DUMP_XML](link, p, line);
+               dp_dump_line(p, line++, "  </info>\n");
+       }
+
        dp_dump_line(p, line++, "</link>\n");
 
 #if 0
@@ -630,6 +725,9 @@ static int link_dump_env(struct nl_object *obj, struct nl_dump_params *p)
                }
        }
 
+       if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_ENV])
+               line = link->l_info_ops->io_dump[NL_DUMP_ENV](link, p, line);
+
        return line;
 }
 
@@ -915,6 +1013,21 @@ struct nl_msg * rtnl_link_build_change_request(struct rtnl_link *old,
        if (tmpl->ce_mask & LINK_ATTR_LINKMODE)
                NLA_PUT_U8(msg, IFLA_LINKMODE, tmpl->l_linkmode);
 
+       if ((tmpl->ce_mask & LINK_ATTR_LINKINFO) && tmpl->l_info_ops &&
+           tmpl->l_info_ops->io_put_attrs) {
+               struct nlattr *info;
+
+               if (!(info = nla_nest_start(msg, IFLA_LINKINFO)))
+                       goto nla_put_failure;
+
+               NLA_PUT_STRING(msg, IFLA_INFO_KIND, tmpl->l_info_ops->io_name);
+
+               if (tmpl->l_info_ops->io_put_attrs(msg, tmpl) < 0)
+                       goto nla_put_failure;
+
+               nla_nest_end(msg, info);
+       }
+
        return msg;
 
 nla_put_failure:
@@ -1379,6 +1492,51 @@ uint64_t rtnl_link_get_stat(struct rtnl_link *link, int id)
        return link->l_stats[id];
 }
 
+/**
+ * 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 nl_error(ENOENT, "No such link info type exists");
+
+       if (link->l_info_ops)
+               release_link_info(link);
+
+       if ((err = io->io_alloc(link)) < 0)
+               return err;
+
+       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 = {
diff --git a/lib/route/link/api.c b/lib/route/link/api.c
new file mode 100644 (file)
index 0000000..afe00b1
--- /dev/null
@@ -0,0 +1,98 @@
+/*
+ * lib/route/link/api.c                Link Info API
+ *
+ *     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-2008 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link
+ * @defgroup link_info Link Info API
+ * @brief
+ *
+ * @par 1) Registering/Unregistering a new link info type
+ * @code
+ * static struct rtnl_link_info_ops vlan_info_ops = {
+ *     .io_name                = "vlan",
+ *     .io_alloc               = vlan_alloc,
+ *     .io_parse               = vlan_parse,
+ *     .io_dump[NL_DUMP_BRIEF] = vlan_dump_brief,
+ *     .io_dump[NL_DUMP_FULL]  = vlan_dump_full,
+ *     .io_free                = vlan_free,
+ * };
+ *
+ * static void __init vlan_init(void)
+ * {
+ *     rtnl_link_register_info(&vlan_info_ops);
+ * }
+ *
+ * static void __exit vlan_exit(void)
+ * {
+ *     rtnl_link_unregister_info(&vlan_info_ops);
+ * }
+ * @endcode
+ *
+ * @{
+ */
+
+#include <netlink-local.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/link.h>
+#include <netlink/route/link/info-api.h>
+
+static struct rtnl_link_info_ops *info_ops;
+
+struct rtnl_link_info_ops *rtnl_link_info_ops_lookup(const char *name)
+{
+       struct rtnl_link_info_ops *ops;
+
+       for (ops = info_ops; ops; ops = ops->io_next)
+               if (!strcmp(ops->io_name, name))
+                       return ops;
+
+       return NULL;
+}
+
+int rtnl_link_register_info(struct rtnl_link_info_ops *ops)
+{
+       if (ops->io_name == NULL)
+               return nl_error(EINVAL, "No name specified");
+
+       if (rtnl_link_info_ops_lookup(ops->io_name))
+               return nl_error(EEXIST, "Link info operations already exist");
+
+       NL_DBG(1, "Registered link info operations %s\n", ops->io_name);
+
+       ops->io_next = info_ops;
+       info_ops = ops;
+
+       return 0;
+}
+
+int rtnl_link_unregister_info(struct rtnl_link_info_ops *ops)
+{
+       struct rtnl_link_info_ops *t, **tp;
+
+       for (tp = &info_ops; (t=*tp) != NULL; tp = &t->io_next)
+               if (t == ops)
+                       break;
+
+       if (!t)
+               return nl_error(ENOENT, "No such link info operations");
+
+       if (t->io_refcnt > 0)
+               return nl_error(EBUSY, "Info operations in use");
+
+       NL_DBG(1, "Unregistered link info perations %s\n", ops->io_name);
+
+       *tp = t->io_next;
+       return 0;
+}
+
+/** @} */
+
diff --git a/lib/route/link/vlan.c b/lib/route/link/vlan.c
new file mode 100644 (file)
index 0000000..5ab9a20
--- /dev/null
@@ -0,0 +1,508 @@
+/*
+ * lib/route/link/vlan.c       VLAN Link Info
+ *
+ *     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-2007 Thomas Graf <tgraf@suug.ch>
+ */
+
+/**
+ * @ingroup link_info
+ * @defgroup vlan VLAN
+ * @brief
+ *
+ * @{
+ */
+
+#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/info-api.h>
+#include <netlink/route/link/vlan.h>
+
+#include <linux/if_vlan.h>
+
+/** @cond SKIP */
+#define VLAN_HAS_ID            (1<<0)
+#define VLAN_HAS_FLAGS         (1<<1)
+#define VLAN_HAS_INGRESS_QOS   (1<<2)
+#define VLAN_HAS_EGRESS_QOS    (1<<3)
+
+struct vlan_info
+{
+       uint16_t                vi_vlan_id;
+       uint32_t                vi_flags;
+       uint32_t                vi_flags_mask;
+       uint32_t                vi_ingress_qos[VLAN_PRIO_MAX+1];
+       uint32_t                vi_negress;
+       uint32_t                vi_egress_size;
+       struct vlan_map *       vi_egress_qos;
+       uint32_t                vi_mask;
+};
+/** @endcond */
+
+static struct trans_tbl vlan_flags[] = {
+       __ADD(VLAN_FLAG_REORDER_HDR, reorder_hdr)
+};
+
+char *rtnl_link_vlan_flags2str(int flags, char *buf, size_t len)
+{
+       return __flags2str(flags, buf, len, vlan_flags, ARRAY_SIZE(vlan_flags));
+}
+
+int rtnl_link_vlan_str2flags(const char *name)
+{
+       return __str2flags(name, vlan_flags, ARRAY_SIZE(vlan_flags));
+}
+
+static struct nla_policy vlan_policy[IFLA_VLAN_MAX+1] = {
+       [IFLA_VLAN_ID]          = { .type = NLA_U16 },
+       [IFLA_VLAN_FLAGS]       = { .minlen = sizeof(struct ifla_vlan_flags) },
+       [IFLA_VLAN_INGRESS_QOS] = { .type = NLA_NESTED },
+       [IFLA_VLAN_EGRESS_QOS]  = { .type = NLA_NESTED },
+};
+
+static int vlan_alloc(struct rtnl_link *link)
+{
+       struct vlan_info *vi;
+
+       if ((vi = calloc(1, sizeof(*vi))) == NULL)
+               return nl_errno(ENOMEM);
+
+       link->l_info = vi;
+
+       return 0;
+}
+
+static int vlan_parse(struct rtnl_link *link, struct nlattr *data,
+                     struct nlattr *xstats)
+{
+       struct nlattr *tb[IFLA_VLAN_MAX+1];
+       struct vlan_info *vi;
+       int err;
+
+       NL_DBG(3, "Parsing VLAN link info");
+
+       if ((err = nla_parse_nested(tb, IFLA_VLAN_MAX, data, vlan_policy)) < 0)
+               goto errout;
+
+       if ((err = vlan_alloc(link)) < 0)
+               goto errout;
+
+       vi = link->l_info;
+
+       if (tb[IFLA_VLAN_ID]) {
+               vi->vi_vlan_id = nla_get_u16(tb[IFLA_VLAN_ID]);
+               vi->vi_mask |= VLAN_HAS_ID;
+       }
+
+       if (tb[IFLA_VLAN_FLAGS]) {
+               struct ifla_vlan_flags flags;
+               nla_memcpy(&flags, tb[IFLA_VLAN_FLAGS], sizeof(flags));
+
+               vi->vi_flags = flags.flags;
+               vi->vi_mask |= VLAN_HAS_FLAGS;
+       }
+
+       if (tb[IFLA_VLAN_INGRESS_QOS]) {
+               struct ifla_vlan_qos_mapping *map;
+               struct nlattr *nla;
+               int remaining;
+
+               memset(vi->vi_ingress_qos, 0, sizeof(vi->vi_ingress_qos));
+
+               nla_for_each_nested(nla, tb[IFLA_VLAN_INGRESS_QOS], remaining) {
+                       if (nla_len(nla) < sizeof(*map))
+                               return nl_error(EINVAL, "Malformed mapping");
+
+                       map = nla_data(nla);
+                       if (map->from < 0 || map->from > VLAN_PRIO_MAX) {
+                               return nl_error(EINVAL, "VLAN prio %d out of "
+                                               "range", map->from);
+                       }
+
+                       vi->vi_ingress_qos[map->from] = map->to;
+               }
+
+               vi->vi_mask |= VLAN_HAS_INGRESS_QOS;
+       }
+
+       if (tb[IFLA_VLAN_EGRESS_QOS]) {
+               struct ifla_vlan_qos_mapping *map;
+               struct nlattr *nla;
+               int remaining, i = 0;
+
+               nla_for_each_nested(nla, tb[IFLA_VLAN_EGRESS_QOS], remaining) {
+                       if (nla_len(nla) < sizeof(*map))
+                               return nl_error(EINVAL, "Malformed mapping");
+                       i++;
+               }
+
+               /* align to have a little reserve */
+               vi->vi_egress_size = (i + 32) & ~31;
+               vi->vi_egress_qos = calloc(vi->vi_egress_size, sizeof(*map));
+               if (vi->vi_egress_qos == NULL)
+                       return nl_errno(ENOMEM);
+
+               i = 0;
+               nla_for_each_nested(nla, tb[IFLA_VLAN_EGRESS_QOS], remaining) {
+                       map = nla_data(nla);
+                       NL_DBG(4, "Assigning egress qos mapping %d\n", i);
+                       vi->vi_egress_qos[i].vm_from = map->from;
+                       vi->vi_egress_qos[i++].vm_to = map->to;
+               }
+
+               vi->vi_negress = i;
+               vi->vi_mask |= VLAN_HAS_EGRESS_QOS;
+       }
+
+       err = 0;
+errout:
+       return err;
+}
+
+static void vlan_free(struct rtnl_link *link)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (vi) {
+               free(vi->vi_egress_qos);
+               vi->vi_egress_qos = NULL;
+       }
+
+       free(vi);
+       link->l_info = NULL;
+}
+
+static int vlan_dump_brief(struct rtnl_link *link, struct nl_dump_params *p,
+                          int line)
+{
+       struct vlan_info *vi = link->l_info;
+
+       dp_dump(p, "vlan-id %d", vi->vi_vlan_id);
+
+       return line;
+}
+
+static int vlan_dump_full(struct rtnl_link *link, struct nl_dump_params *p,
+                         int line)
+{
+       struct vlan_info *vi = link->l_info;
+       int i, printed;
+       char buf[64];
+
+       rtnl_link_vlan_flags2str(vi->vi_flags, buf, sizeof(buf));
+       dp_dump_line(p, line++, "    vlan-info id %d <%s>\n",
+               vi->vi_vlan_id, buf);
+
+       if (vi->vi_mask & VLAN_HAS_INGRESS_QOS) {
+               dp_dump_line(p, line++,
+               "      ingress vlan prio -> qos/socket prio mapping:\n");
+               for (i = 0, printed = 0; i <= VLAN_PRIO_MAX; i++) {
+                       if (vi->vi_ingress_qos[i]) {
+                               if (printed == 0) {
+                                       dp_new_line(p, line);
+                                       dp_dump(p, "      ");
+                               }
+                               dp_dump(p, "%x -> %#08x, ",
+                                       i, vi->vi_ingress_qos[i]);
+                               if (printed++ == 3) {
+                                       dp_dump(p, "\n");
+                                       printed = 0;
+                               }
+                       }
+               }
+
+               if (printed > 0 && printed != 4)
+                       dp_dump(p, "\n");
+       }
+
+       if (vi->vi_mask & VLAN_HAS_EGRESS_QOS) {
+               dp_dump_line(p, line++,
+               "      egress qos/socket prio -> vlan prio mapping:\n");
+               for (i = 0, printed = 0; i < vi->vi_negress; i++) {
+                       if (printed == 0) {
+                               dp_new_line(p, line);
+                               dp_dump(p, "      ");
+                       }
+                       dp_dump(p, "%#08x -> %x, ",
+                               vi->vi_egress_qos[i].vm_from,
+                               vi->vi_egress_qos[i].vm_to);
+                       if (printed++ == 3) {
+                               dp_dump(p, "\n");
+                               printed = 0;
+                       }
+               }
+
+               if (printed > 0 && printed != 4)
+                       dp_dump(p, "\n");
+       }
+
+       return line;
+}
+
+static int vlan_clone(struct rtnl_link *dst, struct rtnl_link *src)
+{
+       struct vlan_info *vdst, *vsrc = src->l_info;
+       int err;
+
+       dst->l_info = NULL;
+       if ((err = rtnl_link_set_info_type(dst, "vlan")) < 0)
+               return err;
+       vdst = dst->l_info;
+
+       vdst->vi_egress_qos = calloc(vsrc->vi_egress_size,
+                                    sizeof(struct vlan_map));
+       if (!vdst->vi_egress_qos)
+               return nl_errno(ENOMEM);
+
+       memcpy(vdst->vi_egress_qos, vsrc->vi_egress_qos,
+              vsrc->vi_egress_size * sizeof(struct vlan_map));
+
+       return 0;
+}
+
+static int vlan_put_attrs(struct nl_msg *msg, struct rtnl_link *link)
+{
+       struct vlan_info *vi = link->l_info;
+       struct nlattr *data;
+
+       if (!(data = nla_nest_start(msg, IFLA_INFO_DATA)))
+               return nl_errno(ENOBUFS);
+
+       if (vi->vi_mask & VLAN_HAS_ID)
+               NLA_PUT_U16(msg, IFLA_VLAN_ID, vi->vi_vlan_id);
+
+       if (vi->vi_mask & VLAN_HAS_FLAGS) {
+               struct ifla_vlan_flags flags = {
+                       .flags = vi->vi_flags,
+                       .mask = vi->vi_flags_mask,
+               };
+
+               NLA_PUT(msg, IFLA_VLAN_FLAGS, sizeof(flags), &flags);
+       }
+
+       if (vi->vi_mask & VLAN_HAS_INGRESS_QOS) {
+               struct ifla_vlan_qos_mapping map;
+               struct nlattr *qos;
+               int i;
+
+               if (!(qos = nla_nest_start(msg, IFLA_VLAN_INGRESS_QOS)))
+                       goto nla_put_failure;
+
+               for (i = 0; i <= VLAN_PRIO_MAX; i++) {
+                       if (vi->vi_ingress_qos[i]) {
+                               map.from = i;
+                               map.to = vi->vi_ingress_qos[i];
+
+                               NLA_PUT(msg, i, sizeof(map), &map);
+                       }
+               }
+
+               nla_nest_end(msg, qos);
+       }
+
+       if (vi->vi_mask & VLAN_HAS_EGRESS_QOS) {
+               struct ifla_vlan_qos_mapping map;
+               struct nlattr *qos;
+               int i;
+
+               if (!(qos = nla_nest_start(msg, IFLA_VLAN_EGRESS_QOS)))
+                       goto nla_put_failure;
+
+               for (i = 0; i < vi->vi_negress; i++) {
+                       map.from = vi->vi_egress_qos[i].vm_from;
+                       map.to = vi->vi_egress_qos[i].vm_to;
+
+                       NLA_PUT(msg, i, sizeof(map), &map);
+               }
+
+               nla_nest_end(msg, qos);
+       }
+
+       nla_nest_end(msg, data);
+
+nla_put_failure:
+
+       return 0;
+}
+
+static struct rtnl_link_info_ops vlan_info_ops = {
+       .io_name                = "vlan",
+       .io_alloc               = vlan_alloc,
+       .io_parse               = vlan_parse,
+       .io_dump[NL_DUMP_BRIEF] = vlan_dump_brief,
+       .io_dump[NL_DUMP_FULL]  = vlan_dump_full,
+       .io_clone               = vlan_clone,
+       .io_free                = vlan_free,
+};
+
+int rtnl_link_vlan_set_id(struct rtnl_link *link, int id)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       vi->vi_vlan_id = id;
+       vi->vi_mask |= VLAN_HAS_ID;
+
+       return 0;
+}
+
+int rtnl_link_vlan_get_id(struct rtnl_link *link)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       if (vi->vi_mask & VLAN_HAS_ID)
+               return vi->vi_vlan_id;
+       else
+               return 0;
+}
+
+int rtnl_link_vlan_set_flags(struct rtnl_link *link, unsigned int flags)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       vi->vi_flags_mask |= flags;
+       vi->vi_flags |= flags;
+       vi->vi_mask |= VLAN_HAS_FLAGS;
+
+       return 0;
+}
+
+int rtnl_link_vlan_unset_flags(struct rtnl_link *link, unsigned int flags)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       vi->vi_flags_mask |= flags;
+       vi->vi_flags &= ~flags;
+       vi->vi_mask |= VLAN_HAS_FLAGS;
+
+       return 0;
+}
+
+unsigned int rtnl_link_vlan_get_flags(struct rtnl_link *link)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       return vi->vi_flags;
+}
+
+int rtnl_link_vlan_set_ingress_map(struct rtnl_link *link, int from,
+                                  uint32_t to)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       if (from < 0 || from > VLAN_PRIO_MAX)
+               return nl_error(EINVAL, "Invalid vlan prio 0..%d",
+                       VLAN_PRIO_MAX);
+
+       vi->vi_ingress_qos[from] = to;
+       vi->vi_mask |= VLAN_HAS_INGRESS_QOS;
+
+       return 0;
+}
+
+uint32_t *rtnl_link_vlan_get_ingress_map(struct rtnl_link *link)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops) {
+               nl_error(EOPNOTSUPP, "Not a VLAN link");
+               return NULL;
+       }
+
+       if (vi->vi_mask & VLAN_HAS_INGRESS_QOS)
+               return vi->vi_ingress_qos;
+       else
+               return NULL;
+}
+
+int rtnl_link_vlan_set_egress_map(struct rtnl_link *link, uint32_t from, int to)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops)
+               return nl_error(EOPNOTSUPP, "Not a VLAN link");
+
+       if (to < 0 || to > VLAN_PRIO_MAX)
+               return nl_error(EINVAL, "Invalid vlan prio 0..%d",
+                       VLAN_PRIO_MAX);
+
+       if (vi->vi_negress >= vi->vi_egress_size) {
+               int new_size = vi->vi_egress_size + 32;
+               void *ptr;
+
+               ptr = realloc(vi->vi_egress_qos, new_size);
+               if (!ptr)
+                       return nl_errno(ENOMEM);
+
+               vi->vi_egress_qos = ptr;
+               vi->vi_egress_size = new_size;
+       }
+
+       vi->vi_egress_qos[vi->vi_negress].vm_from = from;
+       vi->vi_egress_qos[vi->vi_negress].vm_to = to;
+       vi->vi_negress++;
+       vi->vi_mask |= VLAN_HAS_EGRESS_QOS;
+
+       return 0;
+}
+
+struct vlan_map *rtnl_link_vlan_get_egress_map(struct rtnl_link *link,
+                                              int *negress)
+{
+       struct vlan_info *vi = link->l_info;
+
+       if (link->l_info_ops != &vlan_info_ops || !link->l_info_ops) {
+               nl_error(EOPNOTSUPP, "Not a VLAN link");
+               return NULL;
+       }
+
+       if (negress == NULL) {
+               nl_error(EINVAL, "Require pointer to store negress");
+               return NULL;
+       }
+
+       if (vi->vi_mask & VLAN_HAS_EGRESS_QOS) {
+               *negress = vi->vi_negress;
+               return vi->vi_egress_qos;
+       } else {
+               *negress = 0;
+               return NULL;
+       }
+}
+
+static void __init vlan_init(void)
+{
+       rtnl_link_register_info(&vlan_info_ops);
+}
+
+static void __exit vlan_exit(void)
+{
+       rtnl_link_unregister_info(&vlan_info_ops);
+}
+
+/** @} */