mirror of
git://projects.qi-hardware.com/openwrt-xburst.git
synced 2024-08-19 05:38:38 +03:00
Let swconfig provide the cpu port index in its help page. This is needed as e.g. Atheros switches have their cpu port at port 0, not port 5. This could allow e.g. luci to get a rough overview of the layout of the switch. Signed-off-by: Jonas Gorski <jonas.gorski+openwrt@gmail.com> git-svn-id: svn://svn.openwrt.org/openwrt/trunk@20939 3c298f89-4303-0410-b956-a3cf2f4a3e73
665 lines
13 KiB
C
665 lines
13 KiB
C
/*
|
|
* swlib.c: Switch configuration API (user space part)
|
|
*
|
|
* Copyright (C) 2008 Felix Fietkau <nbd@openwrt.org>
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public License
|
|
* version 2.1 as published by the Free Software Foundation.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include <stdlib.h>
|
|
#include <inttypes.h>
|
|
#include <errno.h>
|
|
#include <stdint.h>
|
|
#include <getopt.h>
|
|
#include <sys/types.h>
|
|
#include <sys/socket.h>
|
|
#include <linux/switch.h>
|
|
#include "swlib.h"
|
|
#include <netlink/netlink.h>
|
|
#include <netlink/genl/genl.h>
|
|
#include <netlink/genl/family.h>
|
|
|
|
//#define DEBUG 1
|
|
#ifdef DEBUG
|
|
#define DPRINTF(fmt, ...) fprintf(stderr, "%s(%d): " fmt, __func__, __LINE__, ##__VA_ARGS__)
|
|
#else
|
|
#define DPRINTF(fmt, ...) do {} while (0)
|
|
#endif
|
|
|
|
static struct nl_sock *handle;
|
|
static struct nl_cache *cache;
|
|
static struct genl_family *family;
|
|
static struct nlattr *tb[SWITCH_ATTR_MAX];
|
|
static int refcount = 0;
|
|
|
|
static struct nla_policy port_policy[] = {
|
|
[SWITCH_PORT_ID] = { .type = NLA_U32 },
|
|
[SWITCH_PORT_FLAG_TAGGED] = { .type = NLA_FLAG },
|
|
};
|
|
|
|
static inline void *
|
|
swlib_alloc(size_t size)
|
|
{
|
|
void *ptr;
|
|
|
|
ptr = malloc(size);
|
|
if (!ptr)
|
|
goto done;
|
|
memset(ptr, 0, size);
|
|
|
|
done:
|
|
return ptr;
|
|
}
|
|
|
|
static int
|
|
wait_handler(struct nl_msg *msg, void *arg)
|
|
{
|
|
int *finished = arg;
|
|
|
|
*finished = 1;
|
|
return NL_STOP;
|
|
}
|
|
|
|
/* helper function for performing netlink requests */
|
|
static int
|
|
swlib_call(int cmd, int (*call)(struct nl_msg *, void *),
|
|
int (*data)(struct nl_msg *, void *), void *arg)
|
|
{
|
|
struct nl_msg *msg;
|
|
struct nl_cb *cb = NULL;
|
|
int finished;
|
|
int flags = 0;
|
|
int err;
|
|
|
|
msg = nlmsg_alloc();
|
|
if (!msg) {
|
|
fprintf(stderr, "Out of memory!\n");
|
|
exit(1);
|
|
}
|
|
|
|
if (!data)
|
|
flags |= NLM_F_DUMP;
|
|
|
|
genlmsg_put(msg, NL_AUTO_PID, NL_AUTO_SEQ, genl_family_get_id(family), 0, flags, cmd, 0);
|
|
if (data) {
|
|
if (data(msg, arg) < 0)
|
|
goto nla_put_failure;
|
|
}
|
|
|
|
cb = nl_cb_alloc(NL_CB_CUSTOM);
|
|
if (!cb) {
|
|
fprintf(stderr, "nl_cb_alloc failed.\n");
|
|
exit(1);
|
|
}
|
|
|
|
err = nl_send_auto_complete(handle, msg);
|
|
if (err < 0) {
|
|
fprintf(stderr, "nl_send_auto_complete failed: %d\n", err);
|
|
goto out;
|
|
}
|
|
|
|
finished = 0;
|
|
|
|
if (call)
|
|
nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, call, arg);
|
|
|
|
if (data)
|
|
nl_cb_set(cb, NL_CB_ACK, NL_CB_CUSTOM, wait_handler, &finished);
|
|
else
|
|
nl_cb_set(cb, NL_CB_FINISH, NL_CB_CUSTOM, wait_handler, &finished);
|
|
|
|
err = nl_recvmsgs(handle, cb);
|
|
if (err < 0) {
|
|
goto out;
|
|
}
|
|
|
|
if (!finished)
|
|
err = nl_wait_for_ack(handle);
|
|
|
|
out:
|
|
if (cb)
|
|
nl_cb_put(cb);
|
|
nla_put_failure:
|
|
nlmsg_free(msg);
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
send_attr(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct switch_val *val = arg;
|
|
struct switch_attr *attr = val->attr;
|
|
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_ID, attr->dev->id);
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_OP_ID, attr->id);
|
|
switch(attr->atype) {
|
|
case SWLIB_ATTR_GROUP_PORT:
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_OP_PORT, val->port_vlan);
|
|
break;
|
|
case SWLIB_ATTR_GROUP_VLAN:
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_OP_VLAN, val->port_vlan);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
store_port_val(struct nl_msg *msg, struct nlattr *nla, struct switch_val *val)
|
|
{
|
|
struct nlattr *p;
|
|
int ports = val->attr->dev->ports;
|
|
int err = 0;
|
|
int remaining;
|
|
|
|
if (!val->value.ports)
|
|
val->value.ports = malloc(sizeof(struct switch_port) * ports);
|
|
|
|
nla_for_each_nested(p, nla, remaining) {
|
|
struct nlattr *tb[SWITCH_PORT_ATTR_MAX+1];
|
|
struct switch_port *port;
|
|
|
|
if (val->len >= ports)
|
|
break;
|
|
|
|
err = nla_parse_nested(tb, SWITCH_PORT_ATTR_MAX, p, port_policy);
|
|
if (err < 0)
|
|
goto out;
|
|
|
|
if (!tb[SWITCH_PORT_ID])
|
|
continue;
|
|
|
|
port = &val->value.ports[val->len];
|
|
port->id = nla_get_u32(tb[SWITCH_PORT_ID]);
|
|
port->flags = 0;
|
|
if (tb[SWITCH_PORT_FLAG_TAGGED])
|
|
port->flags |= SWLIB_PORT_FLAG_TAGGED;
|
|
|
|
val->len++;
|
|
}
|
|
|
|
out:
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
store_val(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
|
struct switch_val *val = arg;
|
|
struct switch_attr *attr = val->attr;
|
|
|
|
if (!val)
|
|
goto error;
|
|
|
|
if (nla_parse(tb, SWITCH_ATTR_MAX - 1, genlmsg_attrdata(gnlh, 0),
|
|
genlmsg_attrlen(gnlh, 0), NULL) < 0) {
|
|
goto error;
|
|
}
|
|
|
|
if (tb[SWITCH_ATTR_OP_VALUE_INT])
|
|
val->value.i = nla_get_u32(tb[SWITCH_ATTR_OP_VALUE_INT]);
|
|
else if (tb[SWITCH_ATTR_OP_VALUE_STR])
|
|
val->value.s = strdup(nla_get_string(tb[SWITCH_ATTR_OP_VALUE_STR]));
|
|
else if (tb[SWITCH_ATTR_OP_VALUE_PORTS])
|
|
val->err = store_port_val(msg, tb[SWITCH_ATTR_OP_VALUE_PORTS], val);
|
|
|
|
val->err = 0;
|
|
return 0;
|
|
|
|
error:
|
|
return NL_SKIP;
|
|
}
|
|
|
|
int
|
|
swlib_get_attr(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
int cmd;
|
|
int err;
|
|
|
|
switch(attr->atype) {
|
|
case SWLIB_ATTR_GROUP_GLOBAL:
|
|
cmd = SWITCH_CMD_GET_GLOBAL;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_PORT:
|
|
cmd = SWITCH_CMD_GET_PORT;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_VLAN:
|
|
cmd = SWITCH_CMD_GET_VLAN;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
memset(&val->value, 0, sizeof(val->value));
|
|
val->len = 0;
|
|
val->attr = attr;
|
|
val->err = -EINVAL;
|
|
err = swlib_call(cmd, store_val, send_attr, val);
|
|
if (!err)
|
|
err = val->err;
|
|
|
|
return err;
|
|
}
|
|
|
|
static int
|
|
send_attr_ports(struct nl_msg *msg, struct switch_val *val)
|
|
{
|
|
struct nlattr *n;
|
|
int i;
|
|
|
|
/* TODO implement multipart? */
|
|
if (val->len == 0)
|
|
goto done;
|
|
n = nla_nest_start(msg, SWITCH_ATTR_OP_VALUE_PORTS);
|
|
if (!n)
|
|
goto nla_put_failure;
|
|
for (i = 0; i < val->len; i++) {
|
|
struct switch_port *port = &val->value.ports[i];
|
|
struct nlattr *np;
|
|
|
|
np = nla_nest_start(msg, SWITCH_ATTR_PORT);
|
|
if (!np)
|
|
goto nla_put_failure;
|
|
|
|
NLA_PUT_U32(msg, SWITCH_PORT_ID, port->id);
|
|
if (port->flags & SWLIB_PORT_FLAG_TAGGED)
|
|
NLA_PUT_FLAG(msg, SWITCH_PORT_FLAG_TAGGED);
|
|
|
|
nla_nest_end(msg, np);
|
|
}
|
|
nla_nest_end(msg, n);
|
|
done:
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
send_attr_val(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct switch_val *val = arg;
|
|
struct switch_attr *attr = val->attr;
|
|
|
|
if (send_attr(msg, arg))
|
|
goto nla_put_failure;
|
|
|
|
switch(attr->type) {
|
|
case SWITCH_TYPE_NOVAL:
|
|
break;
|
|
case SWITCH_TYPE_INT:
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_OP_VALUE_INT, val->value.i);
|
|
break;
|
|
case SWITCH_TYPE_STRING:
|
|
if (!val->value.s)
|
|
goto nla_put_failure;
|
|
NLA_PUT_STRING(msg, SWITCH_ATTR_OP_VALUE_STR, val->value.s);
|
|
break;
|
|
case SWITCH_TYPE_PORTS:
|
|
if (send_attr_ports(msg, val) < 0)
|
|
goto nla_put_failure;
|
|
break;
|
|
default:
|
|
goto nla_put_failure;
|
|
}
|
|
return 0;
|
|
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
int
|
|
swlib_set_attr(struct switch_dev *dev, struct switch_attr *attr, struct switch_val *val)
|
|
{
|
|
int cmd;
|
|
|
|
switch(attr->atype) {
|
|
case SWLIB_ATTR_GROUP_GLOBAL:
|
|
cmd = SWITCH_CMD_SET_GLOBAL;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_PORT:
|
|
cmd = SWITCH_CMD_SET_PORT;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_VLAN:
|
|
cmd = SWITCH_CMD_SET_VLAN;
|
|
break;
|
|
default:
|
|
return -EINVAL;
|
|
}
|
|
|
|
val->attr = attr;
|
|
return swlib_call(cmd, NULL, send_attr_val, val);
|
|
}
|
|
|
|
int swlib_set_attr_string(struct switch_dev *dev, struct switch_attr *a, int port_vlan, const char *str)
|
|
{
|
|
struct switch_port *ports;
|
|
struct switch_val val;
|
|
char *ptr;
|
|
|
|
memset(&val, 0, sizeof(val));
|
|
val.port_vlan = port_vlan;
|
|
switch(a->type) {
|
|
case SWITCH_TYPE_INT:
|
|
val.value.i = atoi(str);
|
|
break;
|
|
case SWITCH_TYPE_STRING:
|
|
val.value.s = str;
|
|
break;
|
|
case SWITCH_TYPE_PORTS:
|
|
ports = alloca(sizeof(struct switch_port) * dev->ports);
|
|
memset(ports, 0, sizeof(struct switch_port) * dev->ports);
|
|
val.len = 0;
|
|
ptr = (char *)str;
|
|
while(ptr && *ptr)
|
|
{
|
|
ports[val.len].flags = 0;
|
|
ports[val.len].id = strtoul(ptr, &ptr, 10);
|
|
while(*ptr && !isspace(*ptr)) {
|
|
if (*ptr == 't')
|
|
ports[val.len].flags |= SWLIB_PORT_FLAG_TAGGED;
|
|
ptr++;
|
|
}
|
|
if (*ptr)
|
|
ptr++;
|
|
val.len++;
|
|
}
|
|
val.value.ports = ports;
|
|
break;
|
|
case SWITCH_TYPE_NOVAL:
|
|
break;
|
|
default:
|
|
return -1;
|
|
}
|
|
return swlib_set_attr(dev, a, &val);
|
|
}
|
|
|
|
|
|
struct attrlist_arg {
|
|
int id;
|
|
int atype;
|
|
struct switch_dev *dev;
|
|
struct switch_attr *prev;
|
|
struct switch_attr **head;
|
|
};
|
|
|
|
static int
|
|
add_id(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct attrlist_arg *l = arg;
|
|
|
|
NLA_PUT_U32(msg, SWITCH_ATTR_ID, l->id);
|
|
|
|
return 0;
|
|
nla_put_failure:
|
|
return -1;
|
|
}
|
|
|
|
static int
|
|
add_attr(struct nl_msg *msg, void *ptr)
|
|
{
|
|
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
|
struct attrlist_arg *arg = ptr;
|
|
struct switch_attr *new;
|
|
|
|
if (nla_parse(tb, SWITCH_ATTR_MAX - 1, genlmsg_attrdata(gnlh, 0),
|
|
genlmsg_attrlen(gnlh, 0), NULL) < 0)
|
|
goto done;
|
|
|
|
new = swlib_alloc(sizeof(struct switch_attr));
|
|
if (!new)
|
|
goto done;
|
|
|
|
new->dev = arg->dev;
|
|
new->atype = arg->atype;
|
|
if (arg->prev) {
|
|
arg->prev->next = new;
|
|
} else {
|
|
arg->prev = *arg->head;
|
|
}
|
|
*arg->head = new;
|
|
arg->head = &new->next;
|
|
|
|
if (tb[SWITCH_ATTR_OP_ID])
|
|
new->id = nla_get_u32(tb[SWITCH_ATTR_OP_ID]);
|
|
if (tb[SWITCH_ATTR_OP_TYPE])
|
|
new->type = nla_get_u32(tb[SWITCH_ATTR_OP_TYPE]);
|
|
if (tb[SWITCH_ATTR_OP_NAME])
|
|
new->name = strdup(nla_get_string(tb[SWITCH_ATTR_OP_NAME]));
|
|
if (tb[SWITCH_ATTR_OP_DESCRIPTION])
|
|
new->description = strdup(nla_get_string(tb[SWITCH_ATTR_OP_DESCRIPTION]));
|
|
|
|
done:
|
|
return NL_SKIP;
|
|
}
|
|
|
|
int
|
|
swlib_scan(struct switch_dev *dev)
|
|
{
|
|
struct attrlist_arg arg;
|
|
|
|
if (dev->ops || dev->port_ops || dev->vlan_ops)
|
|
return 0;
|
|
|
|
arg.atype = SWLIB_ATTR_GROUP_GLOBAL;
|
|
arg.dev = dev;
|
|
arg.id = dev->id;
|
|
arg.prev = NULL;
|
|
arg.head = &dev->ops;
|
|
swlib_call(SWITCH_CMD_LIST_GLOBAL, add_attr, add_id, &arg);
|
|
|
|
arg.atype = SWLIB_ATTR_GROUP_PORT;
|
|
arg.prev = NULL;
|
|
arg.head = &dev->port_ops;
|
|
swlib_call(SWITCH_CMD_LIST_PORT, add_attr, add_id, &arg);
|
|
|
|
arg.atype = SWLIB_ATTR_GROUP_VLAN;
|
|
arg.prev = NULL;
|
|
arg.head = &dev->vlan_ops;
|
|
swlib_call(SWITCH_CMD_LIST_VLAN, add_attr, add_id, &arg);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct switch_attr *swlib_lookup_attr(struct switch_dev *dev,
|
|
enum swlib_attr_group atype, const char *name)
|
|
{
|
|
struct switch_attr *head;
|
|
|
|
if (!name || !dev)
|
|
return NULL;
|
|
|
|
switch(atype) {
|
|
case SWLIB_ATTR_GROUP_GLOBAL:
|
|
head = dev->ops;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_PORT:
|
|
head = dev->port_ops;
|
|
break;
|
|
case SWLIB_ATTR_GROUP_VLAN:
|
|
head = dev->vlan_ops;
|
|
break;
|
|
}
|
|
while(head) {
|
|
if (!strcmp(name, head->name))
|
|
return head;
|
|
head = head->next;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void
|
|
swlib_priv_free(void)
|
|
{
|
|
if (cache)
|
|
nl_cache_free(cache);
|
|
if (handle)
|
|
nl_socket_free(handle);
|
|
handle = NULL;
|
|
cache = NULL;
|
|
}
|
|
|
|
static int
|
|
swlib_priv_init(void)
|
|
{
|
|
int ret;
|
|
|
|
handle = nl_socket_alloc();
|
|
if (!handle) {
|
|
DPRINTF("Failed to create handle\n");
|
|
goto err;
|
|
}
|
|
|
|
if (genl_connect(handle)) {
|
|
DPRINTF("Failed to connect to generic netlink\n");
|
|
goto err;
|
|
}
|
|
|
|
ret = genl_ctrl_alloc_cache(handle, &cache);
|
|
if (ret < 0) {
|
|
DPRINTF("Failed to allocate netlink cache\n");
|
|
goto err;
|
|
}
|
|
|
|
family = genl_ctrl_search_by_name(cache, "switch");
|
|
if (!family) {
|
|
DPRINTF("Switch API not present\n");
|
|
goto err;
|
|
}
|
|
return 0;
|
|
|
|
err:
|
|
swlib_priv_free();
|
|
return -EINVAL;
|
|
}
|
|
|
|
struct swlib_scan_arg {
|
|
const char *name;
|
|
struct switch_dev *head;
|
|
struct switch_dev *ptr;
|
|
};
|
|
|
|
static int
|
|
add_switch(struct nl_msg *msg, void *arg)
|
|
{
|
|
struct swlib_scan_arg *sa = arg;
|
|
struct genlmsghdr *gnlh = nlmsg_data(nlmsg_hdr(msg));
|
|
struct switch_dev *dev;
|
|
const char *name;
|
|
|
|
if (nla_parse(tb, SWITCH_ATTR_MAX, genlmsg_attrdata(gnlh, 0), genlmsg_attrlen(gnlh, 0), NULL) < 0)
|
|
goto done;
|
|
|
|
if (!tb[SWITCH_ATTR_DEV_NAME])
|
|
goto done;
|
|
|
|
name = nla_get_string(tb[SWITCH_ATTR_DEV_NAME]);
|
|
if (sa->name && (strcmp(name, sa->name) != 0))
|
|
goto done;
|
|
|
|
dev = swlib_alloc(sizeof(struct switch_dev));
|
|
if (!dev)
|
|
goto done;
|
|
|
|
dev->dev_name = strdup(name);
|
|
if (tb[SWITCH_ATTR_ID])
|
|
dev->id = nla_get_u32(tb[SWITCH_ATTR_ID]);
|
|
if (tb[SWITCH_ATTR_NAME])
|
|
dev->name = strdup(nla_get_string(tb[SWITCH_ATTR_NAME]));
|
|
if (tb[SWITCH_ATTR_PORTS])
|
|
dev->ports = nla_get_u32(tb[SWITCH_ATTR_PORTS]);
|
|
if (tb[SWITCH_ATTR_VLANS])
|
|
dev->vlans = nla_get_u32(tb[SWITCH_ATTR_VLANS]);
|
|
if (tb[SWITCH_ATTR_CPU_PORT])
|
|
dev->cpu_port = nla_get_u32(tb[SWITCH_ATTR_CPU_PORT]);
|
|
|
|
if (!sa->head) {
|
|
sa->head = dev;
|
|
sa->ptr = dev;
|
|
} else {
|
|
sa->ptr->next = dev;
|
|
sa->ptr = dev;
|
|
}
|
|
|
|
refcount++;
|
|
done:
|
|
return NL_SKIP;
|
|
}
|
|
|
|
|
|
struct switch_dev *
|
|
swlib_connect(const char *name)
|
|
{
|
|
struct swlib_scan_arg arg;
|
|
int err;
|
|
|
|
if (!refcount) {
|
|
if (swlib_priv_init() < 0)
|
|
return NULL;
|
|
};
|
|
|
|
arg.head = NULL;
|
|
arg.ptr = NULL;
|
|
arg.name = name;
|
|
swlib_call(SWITCH_CMD_GET_SWITCH, add_switch, NULL, &arg);
|
|
|
|
if (!refcount)
|
|
swlib_priv_free();
|
|
|
|
return arg.head;
|
|
}
|
|
|
|
static void
|
|
swlib_free_attributes(struct switch_attr **head)
|
|
{
|
|
struct switch_attr *a = *head;
|
|
struct switch_attr *next;
|
|
|
|
while (a) {
|
|
next = a->next;
|
|
free(a);
|
|
a = next;
|
|
}
|
|
*head = NULL;
|
|
}
|
|
|
|
void
|
|
swlib_free(struct switch_dev *dev)
|
|
{
|
|
swlib_free_attributes(&dev->ops);
|
|
swlib_free_attributes(&dev->port_ops);
|
|
swlib_free_attributes(&dev->vlan_ops);
|
|
free(dev);
|
|
|
|
if (--refcount == 0)
|
|
swlib_priv_free();
|
|
}
|
|
|
|
void
|
|
swlib_free_all(struct switch_dev *dev)
|
|
{
|
|
struct switch_dev *p;
|
|
|
|
while (dev) {
|
|
p = dev->next;
|
|
swlib_free(dev);
|
|
dev = p;
|
|
}
|
|
}
|