freebsd-dev/sbin/etherswitchcfg/etherswitchcfg.c
Adrian Chadd e6e5db845a Add vlan configuration support to etherswitchcfg.
This adds the support to the config keyword (vlan operation mode), ports
flags, prints the vlan mode and vlan capabilities. It also adds some basic
information to usage() and support the keyword 'help' as a shortcut to
usage(). The manual page is also updated with the new options.

Submitted by:	Luiz Otavio O Souza <loos.br@gmail.com>
Reviewed by:	ray
2013-05-08 20:52:22 +00:00

706 lines
18 KiB
C

/*-
* Copyright (c) 2011-2012 Stefan Bethke.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* $FreeBSD$
*/
#include <sys/cdefs.h>
__FBSDID("$FreeBSD$");
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sysexits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <net/if_media.h>
#include <dev/etherswitch/etherswitch.h>
int get_media_subtype(int, const char *);
int get_media_mode(int, const char *);
int get_media_options(int, const char *);
int lookup_media_word(struct ifmedia_description *, const char *);
void print_media_word(int, int);
void print_media_word_ifconfig(int);
/* some constants */
#define IEEE802DOT1Q_VID_MAX 4094
#define IFMEDIAREQ_NULISTENTRIES 256
enum cmdmode {
MODE_NONE = 0,
MODE_PORT,
MODE_CONFIG,
MODE_VLANGROUP,
MODE_REGISTER,
MODE_PHYREG
};
struct cfg {
int fd;
int verbose;
int mediatypes;
const char *controlfile;
etherswitch_conf_t conf;
etherswitch_info_t info;
enum cmdmode mode;
int unit;
};
struct cmds {
enum cmdmode mode;
const char *name;
int args;
void (*f)(struct cfg *, char *argv[]);
};
static struct cmds cmds[];
/*
* Print a value a la the %b format of the kernel's printf.
* Stolen from ifconfig.c.
*/
static void
printb(const char *s, unsigned v, const char *bits)
{
int i, any = 0;
char c;
if (bits && *bits == 8)
printf("%s=%o", s, v);
else
printf("%s=%x", s, v);
bits++;
if (bits) {
putchar('<');
while ((i = *bits++) != '\0') {
if (v & (1 << (i-1))) {
if (any)
putchar(',');
any = 1;
for (; (c = *bits) > 32; bits++)
putchar(c);
} else
for (; *bits > 32; bits++)
;
}
putchar('>');
}
}
static int
read_register(struct cfg *cfg, int r)
{
struct etherswitch_reg er;
er.reg = r;
if (ioctl(cfg->fd, IOETHERSWITCHGETREG, &er) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETREG)");
return (er.val);
}
static void
write_register(struct cfg *cfg, int r, int v)
{
struct etherswitch_reg er;
er.reg = r;
er.val = v;
if (ioctl(cfg->fd, IOETHERSWITCHSETREG, &er) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETREG)");
}
static int
read_phyregister(struct cfg *cfg, int phy, int reg)
{
struct etherswitch_phyreg er;
er.phy = phy;
er.reg = reg;
if (ioctl(cfg->fd, IOETHERSWITCHGETPHYREG, &er) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPHYREG)");
return (er.val);
}
static void
write_phyregister(struct cfg *cfg, int phy, int reg, int val)
{
struct etherswitch_phyreg er;
er.phy = phy;
er.reg = reg;
er.val = val;
if (ioctl(cfg->fd, IOETHERSWITCHSETPHYREG, &er) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETPHYREG)");
}
static void
set_port_vid(struct cfg *cfg, char *argv[])
{
int v;
etherswitch_port_t p;
v = strtol(argv[1], NULL, 0);
if (v < 0 || v > IEEE802DOT1Q_VID_MAX)
errx(EX_USAGE, "pvid must be between 0 and %d",
IEEE802DOT1Q_VID_MAX);
bzero(&p, sizeof(p));
p.es_port = cfg->unit;
if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
p.es_pvid = v;
if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
}
static void
set_port_flag(struct cfg *cfg, char *argv[])
{
char *flag;
int n;
uint32_t f;
etherswitch_port_t p;
n = 0;
f = 0;
flag = argv[0];
if (strcmp(flag, "none") != 0) {
if (*flag == '-') {
n++;
flag++;
}
if (strcasecmp(flag, "striptag") == 0)
f = ETHERSWITCH_PORT_STRIPTAG;
else if (strcasecmp(flag, "addtag") == 0)
f = ETHERSWITCH_PORT_ADDTAG;
else if (strcasecmp(flag, "firstlock") == 0)
f = ETHERSWITCH_PORT_FIRSTLOCK;
else if (strcasecmp(flag, "dropuntagged") == 0)
f = ETHERSWITCH_PORT_DROPUNTAGGED;
else if (strcasecmp(flag, "doubletag") == 0)
f = ETHERSWITCH_PORT_DOUBLE_TAG;
else if (strcasecmp(flag, "ingress") == 0)
f = ETHERSWITCH_PORT_INGRESS;
}
bzero(&p, sizeof(p));
p.es_port = cfg->unit;
if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
if (n)
p.es_flags &= ~f;
else
p.es_flags |= f;
if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
}
static void
set_port_media(struct cfg *cfg, char *argv[])
{
etherswitch_port_t p;
int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
int subtype;
bzero(&p, sizeof(p));
p.es_port = cfg->unit;
p.es_ifmr.ifm_ulist = ifm_ulist;
p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
subtype = get_media_subtype(IFM_TYPE(ifm_ulist[0]), argv[1]);
p.es_ifr.ifr_media = (p.es_ifmr.ifm_current & IFM_IMASK) |
IFM_TYPE(ifm_ulist[0]) | subtype;
if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
}
static void
set_port_mediaopt(struct cfg *cfg, char *argv[])
{
etherswitch_port_t p;
int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
int options;
bzero(&p, sizeof(p));
p.es_port = cfg->unit;
p.es_ifmr.ifm_ulist = ifm_ulist;
p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
options = get_media_options(IFM_TYPE(ifm_ulist[0]), argv[1]);
if (options == -1)
errx(EX_USAGE, "invalid media options \"%s\"", argv[1]);
if (options & IFM_HDX) {
p.es_ifr.ifr_media &= ~IFM_FDX;
options &= ~IFM_HDX;
}
p.es_ifr.ifr_media |= options;
if (ioctl(cfg->fd, IOETHERSWITCHSETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETPORT)");
}
static void
set_vlangroup_vid(struct cfg *cfg, char *argv[])
{
int v;
etherswitch_vlangroup_t vg;
v = strtol(argv[1], NULL, 0);
if (v < 0 || v >= IEEE802DOT1Q_VID_MAX)
errx(EX_USAGE, "vlan must be between 0 and %d", IEEE802DOT1Q_VID_MAX);
vg.es_vlangroup = cfg->unit;
if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
vg.es_vid = v;
if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
}
static void
set_vlangroup_members(struct cfg *cfg, char *argv[])
{
etherswitch_vlangroup_t vg;
int member, untagged;
char *c, *d;
int v;
member = untagged = 0;
if (strcmp(argv[1], "none") != 0) {
for (c=argv[1]; *c; c=d) {
v = strtol(c, &d, 0);
if (d == c)
break;
if (v < 0 || v >= cfg->info.es_nports)
errx(EX_USAGE, "Member port must be between 0 and %d", cfg->info.es_nports-1);
if (d[0] == ',' || d[0] == '\0' ||
((d[0] == 't' || d[0] == 'T') && (d[1] == ',' || d[1] == '\0'))) {
if (d[0] == 't' || d[0] == 'T') {
untagged &= ~ETHERSWITCH_PORTMASK(v);
d++;
} else
untagged |= ETHERSWITCH_PORTMASK(v);
member |= ETHERSWITCH_PORTMASK(v);
d++;
} else
errx(EX_USAGE, "Invalid members specification \"%s\"", d);
}
}
vg.es_vlangroup = cfg->unit;
if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
vg.es_member_ports = member;
vg.es_untagged_ports = untagged;
if (ioctl(cfg->fd, IOETHERSWITCHSETVLANGROUP, &vg) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETVLANGROUP)");
}
static int
set_register(struct cfg *cfg, char *arg)
{
int a, v;
char *c;
a = strtol(arg, &c, 0);
if (c==arg)
return (1);
if (*c == '=') {
v = strtol(c+1, NULL, 0);
write_register(cfg, a, v);
}
printf("\treg 0x%04x=0x%04x\n", a, read_register(cfg, a));
return (0);
}
static int
set_phyregister(struct cfg *cfg, char *arg)
{
int phy, reg, val;
char *c, *d;
phy = strtol(arg, &c, 0);
if (c==arg)
return (1);
if (*c != '.')
return (1);
d = c+1;
reg = strtol(d, &c, 0);
if (d == c)
return (1);
if (*c == '=') {
val = strtol(c+1, NULL, 0);
write_phyregister(cfg, phy, reg, val);
}
printf("\treg %d.0x%02x=0x%04x\n", phy, reg, read_phyregister(cfg, phy, reg));
return (0);
}
static void
set_vlan_mode(struct cfg *cfg, char *argv[])
{
etherswitch_conf_t conf;
bzero(&conf, sizeof(conf));
conf.cmd = ETHERSWITCH_CONF_VLAN_MODE;
if (strcasecmp(argv[1], "isl") == 0)
conf.vlan_mode = ETHERSWITCH_VLAN_ISL;
else if (strcasecmp(argv[1], "port") == 0)
conf.vlan_mode = ETHERSWITCH_VLAN_PORT;
else if (strcasecmp(argv[1], "dot1q") == 0)
conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q;
else if (strcasecmp(argv[1], "dot1q4k") == 0)
conf.vlan_mode = ETHERSWITCH_VLAN_DOT1Q_4K;
else if (strcasecmp(argv[1], "qinq") == 0)
conf.vlan_mode = ETHERSWITCH_VLAN_DOUBLE_TAG;
else
conf.vlan_mode = 0;
if (ioctl(cfg->fd, IOETHERSWITCHSETCONF, &conf) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHSETCONF)");
}
static void
print_config(struct cfg *cfg)
{
const char *c;
/* Get the device name. */
c = strrchr(cfg->controlfile, '/');
if (c != NULL)
c = c + 1;
else
c = cfg->controlfile;
/* Print VLAN mode. */
if (cfg->conf.cmd & ETHERSWITCH_CONF_VLAN_MODE) {
printf("%s: VLAN mode: ", c);
switch (cfg->conf.vlan_mode) {
case ETHERSWITCH_VLAN_ISL:
printf("ISL\n");
break;
case ETHERSWITCH_VLAN_PORT:
printf("PORT\n");
break;
case ETHERSWITCH_VLAN_DOT1Q:
printf("DOT1Q\n");
break;
case ETHERSWITCH_VLAN_DOT1Q_4K:
printf("DOT1Q4K\n");
break;
case ETHERSWITCH_VLAN_DOUBLE_TAG:
printf("QinQ\n");
break;
default:
printf("none\n");
}
}
}
static void
print_port(struct cfg *cfg, int port)
{
etherswitch_port_t p;
int ifm_ulist[IFMEDIAREQ_NULISTENTRIES];
int i;
bzero(&p, sizeof(p));
p.es_port = port;
p.es_ifmr.ifm_ulist = ifm_ulist;
p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
if (ioctl(cfg->fd, IOETHERSWITCHGETPORT, &p) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETPORT)");
printf("port%d:\n", port);
if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_DOT1Q)
printf("\tpvid: %d\n", p.es_pvid);
printb("\tflags", p.es_flags, ETHERSWITCH_PORT_FLAGS_BITS);
printf("\n");
printf("\tmedia: ");
print_media_word(p.es_ifmr.ifm_current, 1);
if (p.es_ifmr.ifm_active != p.es_ifmr.ifm_current) {
putchar(' ');
putchar('(');
print_media_word(p.es_ifmr.ifm_active, 0);
putchar(')');
}
putchar('\n');
printf("\tstatus: %s\n", (p.es_ifmr.ifm_status & IFM_ACTIVE) != 0 ? "active" : "no carrier");
if (cfg->mediatypes) {
printf("\tsupported media:\n");
if (p.es_ifmr.ifm_count > IFMEDIAREQ_NULISTENTRIES)
p.es_ifmr.ifm_count = IFMEDIAREQ_NULISTENTRIES;
for (i=0; i<p.es_ifmr.ifm_count; i++) {
printf("\t\tmedia ");
print_media_word(ifm_ulist[i], 0);
putchar('\n');
}
}
}
static void
print_vlangroup(struct cfg *cfg, int vlangroup)
{
etherswitch_vlangroup_t vg;
int i, comma;
vg.es_vlangroup = vlangroup;
if (ioctl(cfg->fd, IOETHERSWITCHGETVLANGROUP, &vg) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETVLANGROUP)");
if (vg.es_vid == 0 && vg.es_member_ports == 0)
return;
printf("vlangroup%d:\n", vlangroup);
if (cfg->conf.vlan_mode == ETHERSWITCH_VLAN_PORT)
printf("\tport: %d\n", vg.es_vid);
else
printf("\tvlan: %d\n", vg.es_vid);
printf("\tmembers ");
comma = 0;
if (vg.es_member_ports != 0)
for (i=0; i<cfg->info.es_nports; i++) {
if ((vg.es_member_ports & ETHERSWITCH_PORTMASK(i)) != 0) {
if (comma)
printf(",");
printf("%d", i);
if ((vg.es_untagged_ports & ETHERSWITCH_PORTMASK(i)) == 0)
printf("t");
comma = 1;
}
}
else
printf("none");
printf("\n");
}
static void
print_info(struct cfg *cfg)
{
const char *c;
int i;
c = strrchr(cfg->controlfile, '/');
if (c != NULL)
c = c + 1;
else
c = cfg->controlfile;
if (cfg->verbose) {
printf("%s: %s with %d ports and %d VLAN groups\n", c,
cfg->info.es_name, cfg->info.es_nports,
cfg->info.es_nvlangroups);
printf("%s: ", c);
printb("VLAN capabilities", cfg->info.es_vlan_caps,
ETHERSWITCH_VLAN_CAPS_BITS);
printf("\n");
}
print_config(cfg);
for (i=0; i<cfg->info.es_nports; i++) {
print_port(cfg, i);
}
for (i=0; i<cfg->info.es_nvlangroups; i++) {
print_vlangroup(cfg, i);
}
}
static void
usage(struct cfg *cfg __unused, char *argv[] __unused)
{
fprintf(stderr, "usage: etherswitchctl\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] info\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] config "
"command parameter\n");
fprintf(stderr, "\t\tconfig commands: vlan_mode\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] phy "
"phy.register[=value]\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] portX "
"[flags] command parameter\n");
fprintf(stderr, "\t\tport commands: pvid, media, mediaopt\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] reg "
"register[=value]\n");
fprintf(stderr, "\tetherswitchcfg [-f control file] vlangroupX "
"command parameter\n");
fprintf(stderr, "\t\tvlangroup commands: vlan, members\n");
exit(EX_USAGE);
}
static void
newmode(struct cfg *cfg, enum cmdmode mode)
{
if (mode == cfg->mode)
return;
switch (cfg->mode) {
case MODE_NONE:
break;
case MODE_CONFIG:
/*
* Read the updated the configuration (it can be different
* from the last time we read it).
*/
if (ioctl(cfg->fd, IOETHERSWITCHGETCONF, &cfg->conf) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
print_config(cfg);
break;
case MODE_PORT:
print_port(cfg, cfg->unit);
break;
case MODE_VLANGROUP:
print_vlangroup(cfg, cfg->unit);
break;
case MODE_REGISTER:
case MODE_PHYREG:
break;
}
cfg->mode = mode;
}
int
main(int argc, char *argv[])
{
int ch;
struct cfg cfg;
int i;
bzero(&cfg, sizeof(cfg));
cfg.controlfile = "/dev/etherswitch0";
while ((ch = getopt(argc, argv, "f:mv?")) != -1)
switch(ch) {
case 'f':
cfg.controlfile = optarg;
break;
case 'm':
cfg.mediatypes++;
break;
case 'v':
cfg.verbose++;
break;
case '?':
/* FALLTHROUGH */
default:
usage(&cfg, argv);
}
argc -= optind;
argv += optind;
cfg.fd = open(cfg.controlfile, O_RDONLY);
if (cfg.fd < 0)
err(EX_UNAVAILABLE, "Can't open control file: %s", cfg.controlfile);
if (ioctl(cfg.fd, IOETHERSWITCHGETINFO, &cfg.info) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETINFO)");
if (ioctl(cfg.fd, IOETHERSWITCHGETCONF, &cfg.conf) != 0)
err(EX_OSERR, "ioctl(IOETHERSWITCHGETCONF)");
if (argc == 0) {
print_info(&cfg);
return (0);
}
cfg.mode = MODE_NONE;
while (argc > 0) {
switch(cfg.mode) {
case MODE_NONE:
if (strcmp(argv[0], "info") == 0) {
print_info(&cfg);
} else if (sscanf(argv[0], "port%d", &cfg.unit) == 1) {
if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nports)
errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nports);
newmode(&cfg, MODE_PORT);
} else if (sscanf(argv[0], "vlangroup%d", &cfg.unit) == 1) {
if (cfg.unit < 0 || cfg.unit >= cfg.info.es_nvlangroups)
errx(EX_USAGE, "port unit must be between 0 and %d", cfg.info.es_nvlangroups);
newmode(&cfg, MODE_VLANGROUP);
} else if (strcmp(argv[0], "config") == 0) {
newmode(&cfg, MODE_CONFIG);
} else if (strcmp(argv[0], "phy") == 0) {
newmode(&cfg, MODE_PHYREG);
} else if (strcmp(argv[0], "reg") == 0) {
newmode(&cfg, MODE_REGISTER);
} else if (strcmp(argv[0], "help") == 0) {
usage(&cfg, argv);
} else {
errx(EX_USAGE, "Unknown command \"%s\"", argv[0]);
}
break;
case MODE_PORT:
case MODE_CONFIG:
case MODE_VLANGROUP:
for(i=0; cmds[i].name != NULL; i++) {
if (cfg.mode == cmds[i].mode && strcmp(argv[0], cmds[i].name) == 0) {
if (argc < (cmds[i].args + 1)) {
printf("%s needs an argument\n", cmds[i].name);
break;
}
(cmds[i].f)(&cfg, argv);
argc -= cmds[i].args;
argv += cmds[i].args;
break;
}
}
if (cmds[i].name == NULL) {
newmode(&cfg, MODE_NONE);
continue;
}
break;
case MODE_REGISTER:
if (set_register(&cfg, argv[0]) != 0) {
newmode(&cfg, MODE_NONE);
continue;
}
break;
case MODE_PHYREG:
if (set_phyregister(&cfg, argv[0]) != 0) {
newmode(&cfg, MODE_NONE);
continue;
}
break;
}
argc--;
argv++;
}
/* switch back to command mode to print configuration for last command */
newmode(&cfg, MODE_NONE);
close(cfg.fd);
return (0);
}
static struct cmds cmds[] = {
{ MODE_PORT, "pvid", 1, set_port_vid },
{ MODE_PORT, "media", 1, set_port_media },
{ MODE_PORT, "mediaopt", 1, set_port_mediaopt },
{ MODE_PORT, "addtag", 0, set_port_flag },
{ MODE_PORT, "-addtag", 0, set_port_flag },
{ MODE_PORT, "ingress", 0, set_port_flag },
{ MODE_PORT, "-ingress", 0, set_port_flag },
{ MODE_PORT, "striptag", 0, set_port_flag },
{ MODE_PORT, "-striptag", 0, set_port_flag },
{ MODE_PORT, "doubletag", 0, set_port_flag },
{ MODE_PORT, "-doubletag", 0, set_port_flag },
{ MODE_PORT, "firstlock", 0, set_port_flag },
{ MODE_PORT, "-firstlock", 0, set_port_flag },
{ MODE_PORT, "dropuntagged", 0, set_port_flag },
{ MODE_PORT, "-dropuntagged", 0, set_port_flag },
{ MODE_CONFIG, "vlan_mode", 1, set_vlan_mode },
{ MODE_VLANGROUP, "vlan", 1, set_vlangroup_vid },
{ MODE_VLANGROUP, "members", 1, set_vlangroup_members },
{ 0, NULL, 0, NULL }
};