/* * 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); } /** @} */