/*
* 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-2008 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 -NLE_NOMEM;
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 -NLE_INVAL;
map = nla_data(nla);
if (map->from < 0 || map->from > VLAN_PRIO_MAX) {
return -NLE_INVAL;
}
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 -NLE_INVAL;
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 -NLE_NOMEM;
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 void vlan_dump_line(struct rtnl_link *link, struct nl_dump_params *p)
{
struct vlan_info *vi = link->l_info;
nl_dump(p, "vlan-id %d", vi->vi_vlan_id);
}
static void vlan_dump_details(struct rtnl_link *link, struct nl_dump_params *p)
{
struct vlan_info *vi = link->l_info;
int i, printed;
char buf[64];
rtnl_link_vlan_flags2str(vi->vi_flags, buf, sizeof(buf));
nl_dump_line(p, " vlan-info id %d <%s>\n", vi->vi_vlan_id, buf);
if (vi->vi_mask & VLAN_HAS_INGRESS_QOS) {
nl_dump_line(p,
" 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)
nl_dump_line(p, " ");
nl_dump(p, "%x -> %#08x, ",
i, vi->vi_ingress_qos[i]);
if (printed++ == 3) {
nl_dump(p, "\n");
printed = 0;
}
}
}
if (printed > 0 && printed != 4)
nl_dump(p, "\n");
}
if (vi->vi_mask & VLAN_HAS_EGRESS_QOS) {
nl_dump_line(p,
" egress qos/socket prio -> vlan prio mapping:\n");
for (i = 0, printed = 0; i < vi->vi_negress; i++) {
if (printed == 0)
nl_dump_line(p, " ");
nl_dump(p, "%#08x -> %x, ",
vi->vi_egress_qos[i].vm_from,
vi->vi_egress_qos[i].vm_to);
if (printed++ == 3) {
nl_dump(p, "\n");
printed = 0;
}
}
if (printed > 0 && printed != 4)
nl_dump(p, "\n");
}
}
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 -NLE_NOMEM;
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 -NLE_MSGSIZE;
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_LINE] = vlan_dump_line,
[NL_DUMP_DETAILS] = vlan_dump_details,
},
.io_clone = vlan_clone,
.io_put_attrs = vlan_put_attrs,
.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 -NLE_OPNOTSUPP;
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 -NLE_OPNOTSUPP;
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 -NLE_OPNOTSUPP;
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 -NLE_OPNOTSUPP;
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 -NLE_OPNOTSUPP;
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 -NLE_OPNOTSUPP;
if (from < 0 || from > VLAN_PRIO_MAX)
return -NLE_INVAL;
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)
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 -NLE_OPNOTSUPP;
if (to < 0 || to > VLAN_PRIO_MAX)
return -NLE_INVAL;
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 -NLE_NOMEM;
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)
return NULL;
if (negress == NULL)
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);
}
/** @} */