diff --git a/sbin/ifconfig/ifbridge.c b/sbin/ifconfig/ifbridge.c --- a/sbin/ifconfig/ifbridge.c +++ b/sbin/ifconfig/ifbridge.c @@ -516,7 +516,7 @@ memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst)); req.ifba_flags = IFBAF_STATIC; - req.ifba_vlan = 0; /* XXX allow user to specify */ + req.ifba_vlan = 0; if (do_cmd(ctx, BRDGSADDR, &req, sizeof(req), 1) < 0) err(1, "BRDGSADDR %s", val); @@ -540,6 +540,66 @@ err(1, "BRDGDADDR %s", val); } +static int +setbridge_vstatic(if_ctx *ctx, int argc, const char *const *argv) +{ + struct ifbareq req; + struct ether_addr *ea; + u_long vlan_id; + + if (argc < 3) + errx(1, "usage: vstatic
"); + + if (get_val(argv[2], &vlan_id) < 0 || vlan_id < DOT1Q_VID_MIN || + vlan_id > DOT1Q_VID_MAX) + errx(1, "invalid vlan id: %s", argv[2]); + + memset(&req, 0, sizeof(req)); + strlcpy(req.ifba_ifsname, argv[0], sizeof(req.ifba_ifsname)); + + ea = ether_aton(argv[1]); + if (ea == NULL) + errx(1, "invalid address: %s", argv[1]); + + memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst)); + req.ifba_flags = IFBAF_STATIC; + req.ifba_vlan = vlan_id; + + if (do_cmd(ctx, BRDGSADDR, &req, sizeof(req), 1) < 0) + err(1, "BRDGSADDR %s %s %lu", argv[0], argv[1], vlan_id); + + return 3; +} + +static int +setbridge_vdeladdr(if_ctx *ctx, int argc, const char *const *argv) +{ + struct ifbareq req; + struct ether_addr *ea; + u_long vlan_id; + + if (argc < 2) + errx(1, "usage: vdeladdr
"); + + if (get_val(argv[1], &vlan_id) < 0 || vlan_id < DOT1Q_VID_MIN || + vlan_id > DOT1Q_VID_MAX) + errx(1, "invalid vlan id: %s", argv[1]); + + memset(&req, 0, sizeof(req)); + + ea = ether_aton(argv[0]); + if (ea == NULL) + errx(1, "invalid address: %s", argv[0]); + + memcpy(req.ifba_dst, ea->octet, sizeof(req.ifba_dst)); + req.ifba_vlan = vlan_id; + + if (do_cmd(ctx, BRDGDADDR, &req, sizeof(req), 1) < 0) + err(1, "BRDGDADDR %s %lu", argv[0], vlan_id); + + return 2; +} + static void setbridge_addr(if_ctx *ctx, const char *val __unused, int dummy __unused) { @@ -976,7 +1036,9 @@ DEF_CMD("flush", 0, setbridge_flush), DEF_CMD("flushall", 0, setbridge_flushall), DEF_CMD_ARG2("static", setbridge_static), + DEF_CMD_VARG("vstatic", setbridge_vstatic), DEF_CMD_ARG("deladdr", setbridge_deladdr), + DEF_CMD_VARG("vdeladdr", setbridge_vdeladdr), DEF_CMD("addr", 1, setbridge_addr), DEF_CMD_ARG("maxaddr", setbridge_maxaddr), DEF_CMD_ARG("hellotime", setbridge_hellotime), diff --git a/sbin/ifconfig/ifconfig.h b/sbin/ifconfig/ifconfig.h --- a/sbin/ifconfig/ifconfig.h +++ b/sbin/ifconfig/ifconfig.h @@ -67,6 +67,7 @@ typedef void c_func(if_ctx *ctx, const char *cmd, int arg); typedef void c_func2(if_ctx *ctx, const char *arg1, const char *arg2); typedef void c_func3(if_ctx *ctx, const char *cmd, const char *arg); +typedef int c_funcv(if_ctx *ctx, int argc, const char *const *argv); struct cmd { const char *c_name; @@ -75,11 +76,13 @@ #define NEXTARG2 0xfffffe /* has 2 following args */ #define OPTARG 0xfffffd /* has optional following arg */ #define SPARAM 0xfffffc /* parameter is string c_sparameter */ +#define ARGVECTOR 0xfffffb /* takes argument vector */ const char *c_sparameter; union { c_func *c_func; c_func2 *c_func2; c_func3 *c_func3; + c_funcv *c_funcv; } c_u; int c_iscloneop; struct cmd *c_next; @@ -121,6 +124,13 @@ .c_iscloneop = 0, \ .c_next = NULL, \ } +#define DEF_CMD_VARG(name, func) { \ + .c_name = (name), \ + .c_parameter = ARGVECTOR, \ + .c_u = { .c_funcv = (func) }, \ + .c_iscloneop = 0, \ + .c_next = NULL, \ +} #define DEF_CMD_SARG(name, sparam, func) { \ .c_name = (name), \ .c_parameter = SPARAM, \ diff --git a/sbin/ifconfig/ifconfig.8 b/sbin/ifconfig/ifconfig.8 --- a/sbin/ifconfig/ifconfig.8 +++ b/sbin/ifconfig/ifconfig.8 @@ -28,7 +28,7 @@ .\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF .\" SUCH DAMAGE. .\" -.Dd July 6, 2025 +.Dd July 11, 2025 .Dt IFCONFIG 8 .Os .Sh NAME @@ -2521,14 +2521,24 @@ .It Cm addr Display the addresses that have been learned by the bridge. .It Cm static Ar interface-name Ar address -Add a static entry into the address cache pointing to +Add a static entry into the address cache for VLAN 0 pointing to .Ar interface-name . Static entries are never aged out of the cache or re-placed, even if the address is seen on a different interface. .It Cm deladdr Ar address Delete .Ar address -from the address cache. +from the address cache for VLAN 0. +.It Cm vstatic Ar interface-name Ar address Ar vlan-id +Add a static entry into the address cache for VLAN +.Ar vlan-id +pointing to +.Ar interface-name . +.It Cm vdeladdr Ar address Ar vlan-id +Delete +.Ar address +from the address cache for VLAN +.Ar vlan-id . .It Cm flush Delete all dynamically-learned addresses from the address cache. .It Cm flushall diff --git a/sbin/ifconfig/ifconfig.c b/sbin/ifconfig/ifconfig.c --- a/sbin/ifconfig/ifconfig.c +++ b/sbin/ifconfig/ifconfig.c @@ -1209,6 +1209,13 @@ argc -= 2, argv += 2; } else if (p->c_parameter == SPARAM && p->c_u.c_func3) { p->c_u.c_func3(ctx, *argv, p->c_sparameter); + } else if (p->c_parameter == ARGVECTOR && p->c_u.c_funcv) { + int argsdone; + + argsdone = p->c_u.c_funcv(ctx, argc - 1, + (const char *const *)argv + 1); + argc -= argsdone; + argv += argsdone; } else if (p->c_u.c_func) p->c_u.c_func(ctx, *argv, p->c_parameter); argc--, argv++; diff --git a/tests/sys/net/if_bridge_test.sh b/tests/sys/net/if_bridge_test.sh --- a/tests/sys/net/if_bridge_test.sh +++ b/tests/sys/net/if_bridge_test.sh @@ -245,7 +245,8 @@ jexec one ifconfig ${bridge} static ${epair}a 00:01:02:03:04:05 # List addresses - atf_check -s exit:0 -o ignore \ + atf_check -s exit:0 \ + -o match:"00:01:02:03:04:05 Vlan0 ${epair}a 0 flags=1" \ jexec one ifconfig ${bridge} addr # Delete with bad address format @@ -266,6 +267,72 @@ vnet_cleanup } +atf_test_case "vstatic" "cleanup" +vstatic_head() +{ + atf_set descr 'Bridge VLAN static address test' + atf_set require.user root +} + +vstatic_body() +{ + vnet_init + vnet_init_bridge + + epair=$(vnet_mkepair) + bridge=$(vnet_mkbridge) + + vnet_mkjail one ${bridge} ${epair}a + + ifconfig ${epair}b up + + jexec one ifconfig ${bridge} up + jexec one ifconfig ${epair}a up + jexec one ifconfig ${bridge} addm ${epair}a + + # Wrong interface + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vstatic ${epair}b 00:01:02:03:04:05 10 + + # Bad address format + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vstatic ${epair}a 00:01:02:03:04 10 + + # Invalid VLAN ID + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vstatic ${epair}a 00:01:02:03:04:05 5000 + + # Correct add + atf_check -s exit:0 -o ignore jexec one \ + ifconfig ${bridge} vstatic ${epair}a 00:01:02:03:04:05 10 + + # List addresses + atf_check -s exit:0 \ + -o match:"00:01:02:03:04:05 Vlan10 ${epair}a 0 flags=1" \ + jexec one ifconfig ${bridge} addr + + # Delete with bad address format + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vdeladdr 00:01:02:03:04 10 + + # Delete with unlisted address + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vdeladdr 00:01:02:03:04:06 10 + + # Delete with wrong vlan id + atf_check -s exit:1 -o ignore -e ignore jexec one \ + ifconfig ${bridge} vdeladdr 00:01:02:03:04:05 20 + + # Correct delete + atf_check -s exit:0 -o ignore jexec one \ + ifconfig ${bridge} vdeladdr 00:01:02:03:04:05 10 +} + +vstatic_cleanup() +{ + vnet_cleanup +} + atf_test_case "span" "cleanup" span_head() { @@ -1250,6 +1317,7 @@ atf_add_test_case "stp" atf_add_test_case "stp_vlan" atf_add_test_case "static" + atf_add_test_case "vstatic" atf_add_test_case "span" atf_add_test_case "inherit_mac" atf_add_test_case "delete_with_members"