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