From: Sven Eckelmann Date: Tue, 9 Jul 2019 19:26:46 +0200 Subject: batctl: Make vlan setting explicit The requirement to have a VLAN master device on top of the batadv mesh interface is artificially limiting the capabilities of batctl. Not all master devices in linux which register a VLAN are from type "vlan" and are only registering a single VLAN. For example VLAN aware bridges can create multiple VLANs. These require that the VLAN is identified using the VID and not the vlan device. Signed-off-by: Sven Eckelmann Origin: upstream, https://git.open-mesh.org/batctl.git/commit/4704c5e05af7a4f6a397d80ff80f2f2c56fe8f2c diff --git a/ap_isolation.c b/ap_isolation.c index 71dcd00eac845d488c4969b17e1339f181c6c913..36fd4d607d03768251150951ebe450740501d446 100644 --- a/ap_isolation.c +++ b/ap_isolation.c @@ -28,7 +28,7 @@ static int get_attrs_ap_isolation(struct nl_msg *msg, void *arg) { struct state *state = arg; - if (state->vid >= 0) + if (state->selector == SP_VLAN) nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid); return 0; @@ -38,7 +38,7 @@ static int get_ap_isolation(struct state *state) { enum batadv_nl_commands nl_cmd = BATADV_CMD_SET_MESH; - if (state->vid >= 0) + if (state->selector == SP_VLAN) nl_cmd = BATADV_CMD_GET_VLAN; return sys_simple_nlquery(state, nl_cmd, get_attrs_ap_isolation, @@ -53,7 +53,7 @@ static int set_attrs_ap_isolation(struct nl_msg *msg, void *arg) nla_put_u8(msg, BATADV_ATTR_AP_ISOLATION_ENABLED, data->val); - if (state->vid >= 0) + if (state->selector == SP_VLAN) nla_put_u16(msg, BATADV_ATTR_VLANID, state->vid); return 0; @@ -63,7 +63,7 @@ static int set_ap_isolation(struct state *state) { enum batadv_nl_commands nl_cmd = BATADV_CMD_SET_MESH; - if (state->vid >= 0) + if (state->selector == SP_VLAN) nl_cmd = BATADV_CMD_SET_VLAN; return sys_simple_nlquery(state, nl_cmd, set_attrs_ap_isolation, NULL); @@ -81,3 +81,8 @@ COMMAND_NAMED(SUBCOMMAND, ap_isolation, "ap", handle_sys_setting, COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, &batctl_settings_ap_isolation, "[0|1] \tdisplay or modify ap_isolation setting"); + +COMMAND_NAMED(SUBCOMMAND_VID, ap_isolation, "ap", handle_sys_setting, + COMMAND_FLAG_MESH_IFACE | COMMAND_FLAG_NETLINK, + &batctl_settings_ap_isolation, + "[0|1] \tdisplay or modify ap_isolation setting for vlan device or id"); diff --git a/functions.c b/functions.c index aad6327a8f0fe6e512157e427d88dd0649acd052..61ea4879ebffbdadf8ef5bb12bb737c1ed7ff76f 100644 --- a/functions.c +++ b/functions.c @@ -919,32 +919,44 @@ static int query_rtnl_link_single(int mesh_ifindex, return 0; } -int translate_mesh_iface(struct state *state) +int translate_vlan_iface(struct state *state, const char *vlandev) { struct rtnl_link_iface_data link_data; unsigned int arg_ifindex; - arg_ifindex = if_nametoindex(state->arg_iface); + arg_ifindex = if_nametoindex(vlandev); if (arg_ifindex == 0) - goto fallback_meshif; + return -ENODEV; query_rtnl_link_single(arg_ifindex, &link_data); if (!link_data.vid_found) - goto fallback_meshif; + return -ENODEV; if (!link_data.link_found) - goto fallback_meshif; + return -EINVAL; if (!link_data.kind_found) - goto fallback_meshif; + return -EINVAL; if (strcmp(link_data.kind, "vlan") != 0) - goto fallback_meshif; + return -EINVAL; if (!if_indextoname(link_data.link, state->mesh_iface)) - goto fallback_meshif; + return -ENODEV; state->vid = link_data.vid; + state->selector = SP_VLAN; + + return 0; +} + +int translate_mesh_iface_vlan(struct state *state, const char *vlandev) +{ + int ret; + + ret = translate_vlan_iface(state, vlandev); + if (ret < 0) + goto fallback_meshif; return 0; @@ -952,9 +964,36 @@ int translate_mesh_iface(struct state *state) /* if there is no vid then the argument must be the * mesh interface */ - snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s", - state->arg_iface); - state->vid = -1; + snprintf(state->mesh_iface, sizeof(state->mesh_iface), "%s", vlandev); + state->selector = SP_NONE_OR_MESHIF; + + return 0; +} + +int translate_vid(struct state *state, const char *vidstr) +{ + unsigned long vid; + char *endptr; + + if (vidstr[0] == '\0') { + fprintf(stderr, "Error - unparsable vid\n"); + return -EINVAL; + } + + vid = strtoul(vidstr, &endptr, 0); + if (!endptr || *endptr != '\0') { + fprintf(stderr, "Error - unparsable vid\n"); + return -EINVAL; + } + + if (vid > 4095) { + fprintf(stderr, "Error - too large vid (max 4095)\n"); + return -ERANGE; + } + + /* get mesh interface and overwrite vid afterwards */ + state->vid = vid; + state->selector = SP_VLAN; return 0; } diff --git a/functions.h b/functions.h index d4a556879664eb5b4b11e6c638c22728db7a83a4..7474c40bbcdcb8fac8865def2e82514aede62b69 100644 --- a/functions.h +++ b/functions.h @@ -50,7 +50,9 @@ struct ether_addr *translate_mac(const char *mesh_iface, struct ether_addr *resolve_mac(const char *asc); int query_rtnl_link(int ifindex, nl_recvmsg_msg_cb_t func, void *arg); int netlink_simple_request(struct nl_msg *msg); -int translate_mesh_iface(struct state *state); +int translate_mesh_iface_vlan(struct state *state, const char *vlandev); +int translate_vlan_iface(struct state *state, const char *vlandev); +int translate_vid(struct state *state, const char *vidstr); int get_algoname(const char *mesh_iface, char *algoname, size_t algoname_len); int check_mesh_iface(struct state *state); int check_mesh_iface_ownership(struct state *state, char *hard_iface); diff --git a/main.c b/main.c index 278683c6080e3ff4a9f3225931d0c5eb44f89595..309087799b839848029bd5cbec60cbe1213f9190 100644 --- a/main.c +++ b/main.c @@ -28,48 +28,75 @@ extern const struct command *__stop___command[]; static void print_usage(void) { - enum command_type type[] = { - SUBCOMMAND, - DEBUGTABLE, + struct { + const char *label; + uint32_t types; + } type[] = { + { + .label = "commands:\n", + .types = BIT(SUBCOMMAND) | + BIT(SUBCOMMAND_VID), + }, + { + .label = "debug tables: \tdisplay the corresponding debug table\n", + .types = BIT(DEBUGTABLE), + }, + }; + const char *default_prefixes[] = { + "", + NULL, + }; + const char *vlan_prefixes[] = { + "vlan ", + "vid ", + NULL, }; const struct command **p; - char buf[32]; + const char **prefixes; + const char **prefix; + char buf[64]; size_t i; fprintf(stderr, "Usage: batctl [options] command|debug table [parameters]\n"); fprintf(stderr, "options:\n"); - fprintf(stderr, " \t-m mesh interface or VLAN created on top of a mesh interface (default 'bat0')\n"); + fprintf(stderr, " \t-m mesh interface (default 'bat0')\n"); fprintf(stderr, " \t-h print this help (or 'batctl -h' for the parameter help)\n"); fprintf(stderr, " \t-v print version\n"); for (i = 0; i < sizeof(type) / sizeof(*type); i++) { fprintf(stderr, "\n"); - switch (type[i]) { - case SUBCOMMAND: - fprintf(stderr, "commands:\n"); - break; - case DEBUGTABLE: - fprintf(stderr, "debug tables: \tdisplay the corresponding debug table\n"); - break; - } + fprintf(stderr, "%s", type[i].label); for (p = __start___command; p < __stop___command; p++) { const struct command *cmd = *p; - if (cmd->type != type[i]) + if (!(BIT(cmd->type) & type[i].types)) continue; if (!cmd->usage) continue; - if (strcmp(cmd->name, cmd->abbr) == 0) - snprintf(buf, sizeof(buf), "%s", cmd->name); - else - snprintf(buf, sizeof(buf), "%s|%s", cmd->name, - cmd->abbr); + switch (cmd->type) { + case SUBCOMMAND_VID: + prefixes = vlan_prefixes; + break; + default: + prefixes = default_prefixes; + break; + } - fprintf(stderr, " \t%-27s%s\n", buf, cmd->usage); + for (prefix = &prefixes[0]; *prefix; prefix++) { + if (strcmp(cmd->name, cmd->abbr) == 0) + snprintf(buf, sizeof(buf), "%s%s", + *prefix, cmd->name); + else + snprintf(buf, sizeof(buf), "%s%s|%s", + *prefix, cmd->name, cmd->abbr); + + fprintf(stderr, " \t%-35s%s\n", buf, + cmd->usage); + } } } } @@ -93,13 +120,17 @@ static void version(void) exit(EXIT_SUCCESS); } -static const struct command *find_command(const char *name) +static const struct command *find_command_by_types(uint32_t types, + const char *name) { const struct command **p; for (p = __start___command; p < __stop___command; p++) { const struct command *cmd = *p; + if (!(BIT(cmd->type) & types)) + continue; + if (strcmp(cmd->name, name) == 0) return cmd; @@ -110,13 +141,123 @@ static const struct command *find_command(const char *name) return NULL; } +static const struct command *find_command(struct state *state, const char *name) +{ + uint32_t types; + + switch (state->selector) { + case SP_NONE_OR_MESHIF: + types = BIT(SUBCOMMAND) | + BIT(DEBUGTABLE); + break; + case SP_VLAN: + types = BIT(SUBCOMMAND_VID); + break; + default: + return NULL; + } + + return find_command_by_types(types, name); +} + +static int detect_selector_prefix(int argc, char *argv[], + enum selector_prefix *selector) +{ + /* not enough remaining arguments to detect anything */ + if (argc < 2) + return -EINVAL; + + /* only detect selector prefix which identifies meshif */ + if (strcmp(argv[0], "vlan") == 0) { + *selector = SP_VLAN; + return 2; + } + + return 0; +} + +static int parse_meshif_args(struct state *state, int argc, char *argv[]) +{ + enum selector_prefix selector; + int parsed_args; + char *dev_arg; + int ret; + + parsed_args = detect_selector_prefix(argc, argv, &selector); + if (parsed_args < 1) + goto fallback_meshif_vlan; + + dev_arg = argv[parsed_args - 1]; + + switch (selector) { + case SP_VLAN: + ret = translate_vlan_iface(state, dev_arg); + if (ret < 0) { + fprintf(stderr, "Error - invalid vlan device %s: %s\n", + dev_arg, strerror(-ret)); + return ret; + } + + return parsed_args; + case SP_NONE_OR_MESHIF: + /* not allowed - see detect_selector_prefix */ + break; + } + +fallback_meshif_vlan: + /* parse vlan as part of -m parameter or mesh_dfl_iface */ + translate_mesh_iface_vlan(state, state->arg_iface); + return 0; +} + +static int parse_dev_args(struct state *state, int argc, char *argv[]) +{ + int dev_arguments; + int ret; + + /* try to parse selector prefix which can be used to identify meshif */ + dev_arguments = parse_meshif_args(state, argc, argv); + if (dev_arguments < 0) + return dev_arguments; + + /* try to parse secondary prefix selectors which cannot be used to + * identify the meshif + */ + argv += dev_arguments; + argc -= dev_arguments; + + switch (state->selector) { + case SP_NONE_OR_MESHIF: + /* continue below */ + break; + default: + return dev_arguments; + } + + /* enough room for additional selectors? */ + if (argc < 2) + return dev_arguments; + + if (strcmp(argv[0], "vid") == 0) { + ret = translate_vid(state, argv[1]); + if (ret < 0) + return ret; + + return dev_arguments + 2; + } + + return dev_arguments; +} + int main(int argc, char **argv) { const struct command *cmd; struct state state = { .arg_iface = mesh_dfl_iface, + .selector = SP_NONE_OR_MESHIF, .cmd = NULL, }; + int dev_arguments; int opt; int ret; @@ -152,7 +293,20 @@ int main(int argc, char **argv) argc -= optind; optind = 0; - cmd = find_command(argv[0]); + /* parse arguments to identify vlan, ... */ + dev_arguments = parse_dev_args(&state, argc, argv); + if (dev_arguments < 0) + goto err; + + argv += dev_arguments; + argc -= dev_arguments; + + if (argc == 0) { + fprintf(stderr, "Error - no command specified\n"); + goto err; + } + + cmd = find_command(&state, argv[0]); if (!cmd) { fprintf(stderr, "Error - no valid command or debug table specified: %s\n", @@ -162,8 +316,6 @@ int main(int argc, char **argv) state.cmd = cmd; - translate_mesh_iface(&state); - if (cmd->flags & COMMAND_FLAG_MESH_IFACE && check_mesh_iface(&state) < 0) { fprintf(stderr, diff --git a/main.h b/main.h index 1a4701513c49ad8974b9c9189619f5dde622acd4..efc277c5465942d7b4dba284d29f653273b42dce 100644 --- a/main.h +++ b/main.h @@ -56,13 +56,20 @@ enum command_flags { COMMAND_FLAG_INVERSE = BIT(2), }; +enum selector_prefix { + SP_NONE_OR_MESHIF, + SP_VLAN, +}; + enum command_type { SUBCOMMAND, + SUBCOMMAND_VID, DEBUGTABLE, }; struct state { char *arg_iface; + enum selector_prefix selector; char mesh_iface[IF_NAMESIZE]; unsigned int mesh_ifindex; int vid; @@ -84,7 +91,7 @@ struct command { }; #define COMMAND_NAMED(_type, _name, _abbr, _handler, _flags, _arg, _usage) \ - static const struct command command_ ## _name = { \ + static const struct command command_ ## _name ## _ ## _type = { \ .type = (_type), \ .name = (#_name), \ .abbr = _abbr, \ @@ -93,8 +100,8 @@ struct command { .arg = (_arg), \ .usage = (_usage), \ }; \ - static const struct command *__command_ ## _name \ - __attribute__((__used__)) __attribute__ ((__section__ ("__command"))) = &command_ ## _name + static const struct command *__command_ ## _name ## _ ## _type \ + __attribute__((__used__)) __attribute__ ((__section__ ("__command"))) = &command_ ## _name ## _ ## _type #define COMMAND(_type, _handler, _abbr, _flags, _arg, _usage) \ COMMAND_NAMED(_type, _handler, _abbr, _handler, _flags, _arg, _usage) diff --git a/man/batctl.8 b/man/batctl.8 index 0b430313075b5a7a4c796eba0867954e10061002..a5656cf9059bd82c1b85928c22e30d01c56e475f 100644 --- a/man/batctl.8 +++ b/man/batctl.8 @@ -46,7 +46,7 @@ performances, is also included. .SH OPTIONS .TP .I \fBoptions: -\-m specify mesh interface or VLAN created on top of a mesh interface (default 'bat0') +\-m specify mesh interface (default 'bat0') .br \-h print general batctl help .br @@ -70,7 +70,11 @@ originator interval. The interval is in units of milliseconds. .br .IP "\fBap_isolation\fP|\fBap\fP [\fB0\fP|\fB1\fP]" If no parameter is given the current ap isolation setting is displayed. Otherwise the parameter is used to enable or -disable ap isolation. This command can be used in conjunction with "\-m" option to target per VLAN configurations. +disable ap isolation. +.br +.IP "<\fBvlan \fP|\fBvid \fP> \fBap_isolation\fP|\fBap\fP [\fB0\fP|\fB1\fP]" +If no parameter is given the current ap isolation setting for the specified VLAN is displayed. Otherwise the parameter is used to enable or +disable ap isolation for the specified VLAN. .br .IP "\fBbridge_loop_avoidance\fP|\fBbl\fP [\fB0\fP|\fB1\fP]" If no parameter is given the current bridge loop avoidance setting is displayed. Otherwise the parameter is used to enable diff --git a/sys.c b/sys.c index 39123db87d391b8898b7454eba7708515bfb3c78..61a314d88010ef34507ec9dd6a77b53f318f63a8 100644 --- a/sys.c +++ b/sys.c @@ -141,9 +141,35 @@ int sys_simple_print_boolean(struct nl_msg *msg, void *arg, static void settings_usage(struct state *state) { - fprintf(stderr, "Usage: batctl [options] %s|%s [parameters] %s\n", - state->cmd->name, state->cmd->abbr, - state->cmd->usage ? state->cmd->usage : ""); + const char *default_prefixes[] = { + "", + NULL, + }; + const char *vlan_prefixes[] = { + "vlan ", + "vid ", + NULL, + }; + const char *linestart = "Usage:"; + const char **prefixes; + const char **prefix; + + switch (state->cmd->type) { + case SUBCOMMAND_VID: + prefixes = vlan_prefixes; + break; + default: + prefixes = default_prefixes; + break; + } + + for (prefix = &prefixes[0]; *prefix; prefix++) { + fprintf(stderr, "%s batctl [options] %s%s|%s [parameters] %s\n", + linestart, *prefix, state->cmd->name, state->cmd->abbr, + state->cmd->usage ? state->cmd->usage : ""); + + linestart = " "; + } fprintf(stderr, "parameters:\n"); fprintf(stderr, " \t -h print this help\n"); @@ -233,15 +259,19 @@ int handle_sys_setting(struct state *state, int argc, char **argv) return EXIT_FAILURE; } - /* if the specified interface is a VLAN then change the path to point - * to the proper "vlan%{vid}" subfolder in the sysfs tree. - */ - if (state->vid >= 0) - snprintf(path_buff, PATH_BUFF_LEN, SYS_VLAN_PATH, - state->mesh_iface, state->vid); - else + switch (state->selector) { + case SP_NONE_OR_MESHIF: snprintf(path_buff, PATH_BUFF_LEN, SYS_BATIF_PATH_FMT, state->mesh_iface); + break; + case SP_VLAN: + /* if the specified interface is a VLAN then change the path to + * point to the proper "vlan%{vid}" subfolder in the sysfs tree. + */ + snprintf(path_buff, PATH_BUFF_LEN, SYS_VLAN_PATH, + state->mesh_iface, state->vid); + break; + } if (argc == 1) { res = sys_read_setting(state, path_buff, settings->sysfs_name);