From a7469ce758fac3631df6ce72eb3f89150070e7f8 Mon Sep 17 00:00:00 2001 From: Thomas Graf Date: Tue, 8 Jan 2008 15:00:46 +0100 Subject: [PATCH] Link info interface and vlan support Adds an external interface to implement link info types and implements the type vlan. --- include/linux/if_link.h | 48 +++ include/netlink-types.h | 3 + include/netlink/route/link.h | 4 + include/netlink/route/link/info-api.h | 71 ++++ include/netlink/route/link/vlan.h | 55 +++ lib/Makefile | 2 + lib/route/link.c | 180 ++++++++- lib/route/link/api.c | 98 +++++ lib/route/link/vlan.c | 508 ++++++++++++++++++++++++++ 9 files changed, 958 insertions(+), 11 deletions(-) create mode 100644 include/netlink/route/link/info-api.h create mode 100644 include/netlink/route/link/vlan.h create mode 100644 lib/route/link/api.c create mode 100644 lib/route/link/vlan.c diff --git a/include/linux/if_link.h b/include/linux/if_link.h index 604c243..84c3492 100644 --- a/include/linux/if_link.h +++ b/include/linux/if_link.h @@ -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 */ diff --git a/include/netlink-types.h b/include/netlink-types.h index 5a23450..f273da3 100644 --- a/include/netlink-types.h +++ b/include/netlink-types.h @@ -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 diff --git a/include/netlink/route/link.h b/include/netlink/route/link.h index 78f2b0b..caaa792 100644 --- a/include/netlink/route/link.h +++ b/include/netlink/route/link.h @@ -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 index 0000000..2ccce9d --- /dev/null +++ b/include/netlink/route/link/info-api.h @@ -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 + */ + +#ifndef NETLINK_LINK_INFO_API_H_ +#define NETLINK_LINK_INFO_API_H_ + +#include + +#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 index 0000000..a3ad76d --- /dev/null +++ b/include/netlink/route/link/vlan.h @@ -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 + */ + +#ifndef NETLINK_LINK_VLAN_H_ +#define NETLINK_LINK_VLAN_H_ + +#include +#include + +#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 diff --git a/lib/Makefile b/lib/Makefile index a654242..0bf8af7 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -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 diff --git a/lib/route/link.c b/lib/route/link.c index 2fec334..ab89c24 100644 --- a/lib/route/link.c +++ b/lib/route/link.c @@ -123,6 +123,27 @@ * // 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 * @{ */ @@ -133,6 +154,7 @@ #include #include #include +#include /** @cond SKIP */ #define LINK_ATTR_MTU 0x0001 @@ -153,16 +175,33 @@ #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++, " \n"); } + if (link->l_info_ops && link->l_info_ops->io_dump[NL_DUMP_XML]) { + dp_dump_line(p, line++, " \n"); + line = link->l_info_ops->io_dump[NL_DUMP_XML](link, p, line); + dp_dump_line(p, line++, " \n"); + } + dp_dump_line(p, line++, "\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 index 0000000..afe00b1 --- /dev/null +++ b/lib/route/link/api.c @@ -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 + */ + +/** + * @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 +#include +#include +#include +#include + +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 index 0000000..5ab9a20 --- /dev/null +++ b/lib/route/link/vlan.c @@ -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 + */ + +/** + * @ingroup link_info + * @defgroup vlan VLAN + * @brief + * + * @{ + */ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +/** @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); +} + +/** @} */