diff --git a/sbin/ifconfig/ifvlan.c b/sbin/ifconfig/ifvlan.c index a79ea35bc14b..0a2603f1736f 100644 --- a/sbin/ifconfig/ifvlan.c +++ b/sbin/ifconfig/ifvlan.c @@ -1,311 +1,332 @@ /*- * SPDX-License-Identifier: BSD-4-Clause * * Copyright (c) 1999 Bill Paul * Copyright (c) 2012 ADARA Networks, Inc. * All rights reserved. * * Portions of this software were developed by Robert N. M. Watson under * contract to ADARA Networks, Inc. * * 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. * 3. All advertising materials mentioning features or use of this software * must display the following acknowledgement: * This product includes software developed by Bill Paul. * 4. Neither the name of the author nor the names of any co-contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY Bill Paul 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 Bill Paul OR THE VOICES IN HIS HEAD * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "ifconfig.h" #define NOTAG ((u_short) -1) +#define NOPROTO ((u_short) -1) static const char proto_8021Q[] = "802.1q"; static const char proto_8021ad[] = "802.1ad"; static const char proto_qinq[] = "qinq"; static struct vlanreq params = { .vlr_tag = NOTAG, - .vlr_proto = ETHERTYPE_VLAN, + .vlr_proto = NOPROTO, }; static void vlan_status(if_ctx *ctx) { struct vlanreq vreq = {}; struct ifreq ifr = { .ifr_data = (caddr_t)&vreq }; if (ioctl_ctx_ifr(ctx, SIOCGETVLAN, &ifr) == -1) return; printf("\tvlan: %d", vreq.vlr_tag); printf(" vlanproto: "); switch (vreq.vlr_proto) { case ETHERTYPE_VLAN: printf(proto_8021Q); break; case ETHERTYPE_QINQ: printf(proto_8021ad); break; default: printf("0x%04x", vreq.vlr_proto); } if (ioctl_ctx_ifr(ctx, SIOCGVLANPCP, &ifr) != -1) printf(" vlanpcp: %u", ifr.ifr_vlan_pcp); printf(" parent interface: %s", vreq.vlr_parent[0] == '\0' ? "" : vreq.vlr_parent); printf("\n"); } static int vlan_match_ethervid(const char *name) { return (strchr(name, '.') != NULL); } static void vlan_parse_ethervid(const char *name) { char ifname[IFNAMSIZ]; char *cp; unsigned int vid; strlcpy(ifname, name, IFNAMSIZ); if ((cp = strrchr(ifname, '.')) == NULL) return; /* * Derive params from interface name: "parent.vid". */ *cp++ = '\0'; if ((*cp < '1') || (*cp > '9')) errx(1, "invalid vlan tag"); vid = *cp++ - '0'; while ((*cp >= '0') && (*cp <= '9')) { vid = (vid * 10) + (*cp++ - '0'); if (vid >= 0xFFF) errx(1, "invalid vlan tag"); } if (*cp != '\0') errx(1, "invalid vlan tag"); /* * allow "devX.Y vlandev devX vlan Y" syntax */ if (params.vlr_tag == NOTAG || params.vlr_tag == vid) params.vlr_tag = vid; else errx(1, "ambiguous vlan specification"); /* Restrict overriding interface name */ if (params.vlr_parent[0] == '\0' || !strcmp(params.vlr_parent, ifname)) strlcpy(params.vlr_parent, ifname, IFNAMSIZ); else errx(1, "ambiguous vlan specification"); } static void vlan_create(if_ctx *ctx, struct ifreq *ifr) { vlan_parse_ethervid(ifr->ifr_name); if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0') { /* * One or both parameters were specified, make sure both. */ if (params.vlr_tag == NOTAG) errx(1, "must specify a tag for vlan create"); if (params.vlr_parent[0] == '\0') errx(1, "must specify a parent device for vlan create"); + if (params.vlr_proto == NOPROTO) + params.vlr_proto = ETHERTYPE_VLAN; ifr->ifr_data = (caddr_t) ¶ms; } ifcreate_ioctl(ctx, ifr); } static void vlan_cb(if_ctx *ctx __unused, void *arg __unused) { if ((params.vlr_tag != NOTAG) ^ (params.vlr_parent[0] != '\0')) errx(1, "both vlan and vlandev must be specified"); } static void vlan_set(int s, struct ifreq *ifr) { if (params.vlr_tag != NOTAG && params.vlr_parent[0] != '\0') { + if (params.vlr_proto == NOPROTO) + params.vlr_proto = ETHERTYPE_VLAN; ifr->ifr_data = (caddr_t) ¶ms; if (ioctl(s, SIOCSETVLAN, (caddr_t)ifr) == -1) err(1, "SIOCSETVLAN"); } } static void setvlantag(if_ctx *ctx, const char *val, int dummy __unused) { struct vlanreq vreq = {}; struct ifreq ifr = { .ifr_data = (caddr_t)&vreq }; u_long ul; char *endp; ul = strtoul(val, &endp, 0); if (*endp != '\0') errx(1, "invalid value for vlan"); params.vlr_tag = ul; /* check if the value can be represented in vlr_tag */ if (params.vlr_tag != ul) errx(1, "value for vlan out of range"); if (ioctl_ctx_ifr(ctx, SIOCGETVLAN, &ifr) != -1) { - vreq.vlr_tag = params.vlr_tag; - memcpy(¶ms, &vreq, sizeof(params)); + /* + * Retrieve the current settings if the interface has already + * been configured. + */ + if (vreq.vlr_parent[0] != '\0') { + if (params.vlr_parent[0] == '\0') + strlcpy(params.vlr_parent, vreq.vlr_parent, IFNAMSIZ); + if (params.vlr_proto == NOPROTO) + params.vlr_proto = vreq.vlr_proto; + } vlan_set(ctx->io_s, &ifr); } } static void setvlandev(if_ctx *ctx, const char *val, int dummy __unused) { struct vlanreq vreq = {}; struct ifreq ifr = { .ifr_data = (caddr_t)&vreq }; strlcpy(params.vlr_parent, val, sizeof(params.vlr_parent)); if (ioctl_ctx_ifr(ctx, SIOCGETVLAN, &ifr) != -1) vlan_set(ctx->io_s, &ifr); } static void setvlanproto(if_ctx *ctx, const char *val, int dummy __unused) { struct vlanreq vreq = {}; struct ifreq ifr = { .ifr_data = (caddr_t)&vreq }; if (strncasecmp(proto_8021Q, val, strlen(proto_8021Q)) == 0) { params.vlr_proto = ETHERTYPE_VLAN; } else if ((strncasecmp(proto_8021ad, val, strlen(proto_8021ad)) == 0) || (strncasecmp(proto_qinq, val, strlen(proto_qinq)) == 0)) { params.vlr_proto = ETHERTYPE_QINQ; } else errx(1, "invalid value for vlanproto"); if (ioctl_ctx_ifr(ctx, SIOCGETVLAN, &ifr) != -1) { - vreq.vlr_proto = params.vlr_proto; - memcpy(¶ms, &vreq, sizeof(params)); + /* + * Retrieve the current settings if the interface has already + * been configured. + */ + if (vreq.vlr_parent[0] != '\0') { + if (params.vlr_parent[0] == '\0') + strlcpy(params.vlr_parent, vreq.vlr_parent, IFNAMSIZ); + if (params.vlr_tag == NOTAG) + params.vlr_tag = vreq.vlr_tag; + } vlan_set(ctx->io_s, &ifr); } } static void setvlanpcp(if_ctx *ctx, const char *val, int dummy __unused) { u_long ul; char *endp; struct ifreq ifr = {}; ul = strtoul(val, &endp, 0); if (*endp != '\0') errx(1, "invalid value for vlanpcp"); if (ul > 7) errx(1, "value for vlanpcp out of range"); ifr.ifr_vlan_pcp = ul; if (ioctl_ctx_ifr(ctx, SIOCSVLANPCP, &ifr) == -1) err(1, "SIOCSVLANPCP"); } static void unsetvlandev(if_ctx *ctx, const char *val __unused, int dummy __unused) { struct vlanreq vreq = {}; struct ifreq ifr = { .ifr_data = (caddr_t)&vreq }; if (ioctl_ctx_ifr(ctx, SIOCGETVLAN, &ifr) == -1) err(1, "SIOCGETVLAN"); bzero((char *)&vreq.vlr_parent, sizeof(vreq.vlr_parent)); vreq.vlr_tag = 0; if (ioctl_ctx(ctx, SIOCSETVLAN, (caddr_t)&ifr) == -1) err(1, "SIOCSETVLAN"); } static struct cmd vlan_cmds[] = { DEF_CLONE_CMD_ARG("vlan", setvlantag), DEF_CLONE_CMD_ARG("vlandev", setvlandev), DEF_CLONE_CMD_ARG("vlanproto", setvlanproto), DEF_CMD_ARG("vlanpcp", setvlanpcp), /* NB: non-clone cmds */ DEF_CMD_ARG("vlan", setvlantag), DEF_CMD_ARG("vlandev", setvlandev), DEF_CMD_ARG("vlanproto", setvlanproto), /* XXX For compatibility. Should become DEF_CMD() some day. */ DEF_CMD_OPTARG("-vlandev", unsetvlandev), DEF_CMD("vlanmtu", IFCAP_VLAN_MTU, setifcap), DEF_CMD("-vlanmtu", IFCAP_VLAN_MTU, clearifcap), DEF_CMD("vlanhwtag", IFCAP_VLAN_HWTAGGING, setifcap), DEF_CMD("-vlanhwtag", IFCAP_VLAN_HWTAGGING, clearifcap), DEF_CMD("vlanhwfilter", IFCAP_VLAN_HWFILTER, setifcap), DEF_CMD("-vlanhwfilter", IFCAP_VLAN_HWFILTER, clearifcap), DEF_CMD("vlanhwtso", IFCAP_VLAN_HWTSO, setifcap), DEF_CMD("-vlanhwtso", IFCAP_VLAN_HWTSO, clearifcap), DEF_CMD("vlanhwcsum", IFCAP_VLAN_HWCSUM, setifcap), DEF_CMD("-vlanhwcsum", IFCAP_VLAN_HWCSUM, clearifcap), }; static struct afswtch af_vlan = { .af_name = "af_vlan", .af_af = AF_UNSPEC, .af_other_status = vlan_status, }; static __constructor void vlan_ctor(void) { size_t i; for (i = 0; i < nitems(vlan_cmds); i++) cmd_register(&vlan_cmds[i]); af_register(&af_vlan); callback_register(vlan_cb, NULL); clone_setdefcallback_prefix("vlan", vlan_create); clone_setdefcallback_filter(vlan_match_ethervid, vlan_create); } diff --git a/tests/sys/net/if_vlan.sh b/tests/sys/net/if_vlan.sh index 675ed0090e8c..458e3cc36bc6 100755 --- a/tests/sys/net/if_vlan.sh +++ b/tests/sys/net/if_vlan.sh @@ -1,305 +1,309 @@ . $(atf_get_srcdir)/../common/vnet.subr atf_test_case "basic" "cleanup" basic_head() { atf_set descr 'Basic VLAN test' atf_set require.user root } basic_body() { vnet_init epair_vlan=$(vnet_mkepair) vnet_mkjail alcatraz ${epair_vlan}a vnet_mkjail singsing ${epair_vlan}b vlan0=$(jexec alcatraz ifconfig vlan create vlandev ${epair_vlan}a \ vlan 42) jexec alcatraz ifconfig ${epair_vlan}a up jexec alcatraz ifconfig ${vlan0} 10.0.0.1/24 up - vlan1=$(jexec singsing ifconfig vlan create vlandev ${epair_vlan}b \ - vlan 42) + vlan1=$(jexec singsing ifconfig vlan create) + + # Test associating the physical interface + atf_check -s exit:0 \ + jexec singsing ifconfig ${vlan1} vlandev ${epair_vlan}b vlan 42 + jexec singsing ifconfig ${epair_vlan}b up jexec singsing ifconfig ${vlan1} 10.0.0.2/24 up atf_check -s exit:0 -o ignore jexec singsing ping -c 1 10.0.0.1 # Test changing the vlan ID atf_check -s exit:0 \ jexec singsing ifconfig ${vlan1} vlandev ${epair_vlan}b vlan 43 atf_check -s exit:2 -o ignore jexec singsing ping -c 1 10.0.0.1 # And change back # Test changing the vlan ID atf_check -s exit:0 \ - jexec singsing ifconfig ${vlan1} vlandev ${epair_vlan}b vlan 42 + jexec singsing ifconfig ${vlan1} vlan 42 vlandev ${epair_vlan}b atf_check -s exit:0 -o ignore jexec singsing ping -c 1 10.0.0.1 } basic_cleanup() { vnet_cleanup } # Simple Q-in-Q (802.1Q over 802.1ad) atf_test_case "qinq_simple" "cleanup" qinq_simple_head() { atf_set descr 'Simple Q-in-Q test (802.1Q over 802.1ad)' atf_set require.user root } qinq_simple_body() { vnet_init epair_qinq=$(vnet_mkepair) vnet_mkjail jqinq0 ${epair_qinq}a vnet_mkjail jqinq1 ${epair_qinq}b vlan5a=$(jexec jqinq0 ifconfig vlan create \ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad) vlan42a=$(jexec jqinq0 ifconfig vlan create \ vlandev ${vlan5a} vlan 42 vlanproto 802.1q) jexec jqinq0 ifconfig ${epair_qinq}a up jexec jqinq0 ifconfig ${vlan5a} up jexec jqinq0 ifconfig ${vlan42a} 10.5.42.1/24 up vlan5b=$(jexec jqinq1 ifconfig vlan create \ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad) vlan42b=$(jexec jqinq1 ifconfig vlan create \ vlandev ${vlan5b} vlan 42 vlanproto 802.1q) jexec jqinq1 ifconfig ${epair_qinq}b up jexec jqinq1 ifconfig ${vlan5b} up jexec jqinq1 ifconfig ${vlan42b} 10.5.42.2/24 up atf_check -s exit:0 -o ignore jexec jqinq1 ping -c 1 10.5.42.1 } qinq_simple_cleanup() { vnet_cleanup } # Deep Q-in-Q (802.1Q over 802.1ad over 802.1ad) atf_test_case "qinq_deep" "cleanup" qinq_deep_head() { atf_set descr 'Deep Q-in-Q test (802.1Q over 802.1ad over 802.1ad)' atf_set require.user root } qinq_deep_body() { vnet_init epair_qinq=$(vnet_mkepair) vnet_mkjail jqinq2 ${epair_qinq}a vnet_mkjail jqinq3 ${epair_qinq}b vlan5a=$(jexec jqinq2 ifconfig vlan create \ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad) vlan6a=$(jexec jqinq2 ifconfig vlan create \ vlandev ${vlan5a} vlan 6 vlanproto 802.1ad) vlan42a=$(jexec jqinq2 ifconfig vlan create \ vlandev ${vlan6a} vlan 42 vlanproto 802.1q) jexec jqinq2 ifconfig ${epair_qinq}a up jexec jqinq2 ifconfig ${vlan5a} up jexec jqinq2 ifconfig ${vlan6a} up jexec jqinq2 ifconfig ${vlan42a} 10.6.42.1/24 up vlan5b=$(jexec jqinq3 ifconfig vlan create \ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad) vlan6b=$(jexec jqinq3 ifconfig vlan create \ vlandev ${vlan5b} vlan 6 vlanproto 802.1ad) vlan42b=$(jexec jqinq3 ifconfig vlan create \ vlandev ${vlan6b} vlan 42 vlanproto 802.1q) jexec jqinq3 ifconfig ${epair_qinq}b up jexec jqinq3 ifconfig ${vlan5b} up jexec jqinq3 ifconfig ${vlan6b} up jexec jqinq3 ifconfig ${vlan42b} 10.6.42.2/24 up atf_check -s exit:0 -o ignore jexec jqinq3 ping -c 1 10.6.42.1 } qinq_deep_cleanup() { vnet_cleanup } # Legacy Q-in-Q (802.1Q over 802.1Q) atf_test_case "qinq_legacy" "cleanup" qinq_legacy_head() { atf_set descr 'Legacy Q-in-Q test (802.1Q over 802.1Q)' atf_set require.user root } qinq_legacy_body() { vnet_init epair_qinq=$(vnet_mkepair) vnet_mkjail jqinq4 ${epair_qinq}a vnet_mkjail jqinq5 ${epair_qinq}b vlan5a=$(jexec jqinq4 ifconfig vlan create \ vlandev ${epair_qinq}a vlan 5) vlan42a=$(jexec jqinq4 ifconfig vlan create \ vlandev ${vlan5a} vlan 42) jexec jqinq4 ifconfig ${epair_qinq}a up jexec jqinq4 ifconfig ${vlan5a} up jexec jqinq4 ifconfig ${vlan42a} 10.5.42.1/24 up vlan5b=$(jexec jqinq5 ifconfig vlan create \ vlandev ${epair_qinq}b vlan 5) vlan42b=$(jexec jqinq5 ifconfig vlan create \ vlandev ${vlan5b} vlan 42) jexec jqinq5 ifconfig ${epair_qinq}b up jexec jqinq5 ifconfig ${vlan5b} up jexec jqinq5 ifconfig ${vlan42b} 10.5.42.2/24 up atf_check -s exit:0 -o ignore jexec jqinq5 ping -c 1 10.5.42.1 } qinq_legacy_cleanup() { vnet_cleanup } # Simple Q-in-Q with dot notation atf_test_case "qinq_dot" "cleanup" qinq_dot_head() { atf_set descr 'Simple Q-in-Q test with dot notation' atf_set require.user root } qinq_dot_body() { vnet_init epair_qinq=$(vnet_mkepair) vnet_mkjail jqinq6 ${epair_qinq}a vnet_mkjail jqinq7 ${epair_qinq}b jexec jqinq6 ifconfig vlan5 create \ vlandev ${epair_qinq}a vlan 5 vlanproto 802.1ad jexec jqinq6 ifconfig vlan5.42 create \ vlanproto 802.1q jexec jqinq6 ifconfig ${epair_qinq}a up jexec jqinq6 ifconfig vlan5 up jexec jqinq6 ifconfig vlan5.42 10.5.42.1/24 up vlan5b=$(jexec jqinq7 ifconfig vlan create \ vlandev ${epair_qinq}b vlan 5 vlanproto 802.1ad) vlan42b=$(jexec jqinq7 ifconfig vlan create \ vlandev ${vlan5b} vlan 42 vlanproto 802.1q) jexec jqinq7 ifconfig ${epair_qinq}b up jexec jqinq7 ifconfig ${vlan5b} up jexec jqinq7 ifconfig ${vlan42b} 10.5.42.2/24 up atf_check -s exit:0 -o ignore jexec jqinq7 ping -c 1 10.5.42.1 } qinq_dot_cleanup() { vnet_cleanup } atf_test_case "qinq_setflags" "cleanup" qinq_setflags_head() { atf_set descr 'Test setting flags on a QinQ device' atf_set require.user root } qinq_setflags_body() { vnet_init epair=$(vnet_mkepair) ifconfig ${epair}a up vlan1=$(ifconfig vlan create) ifconfig $vlan1 vlan 1 vlandev ${epair}a vlan2=$(ifconfig vlan create) ifconfig $vlan2 vlan 2 vlandev $vlan1 # This panics, incorrect locking ifconfig $vlan2 promisc } qinq_setflags_cleanup() { vnet_cleanup } atf_test_case "bpf_pcp" "cleanup" bpf_pcp_head() { atf_set descr 'Set VLAN PCP through BPF' atf_set require.user root atf_set require.progs scapy } bpf_pcp_body() { vnet_init epair=$(vnet_mkepair) ifconfig ${epair}a up vnet_mkjail alcatraz ${epair}b vlan=$(jexec alcatraz ifconfig vlan create) jexec alcatraz ifconfig ${vlan} vlan 42 vlandev ${epair}b jexec alcatraz ifconfig ${vlan} up jexec alcatraz ifconfig ${epair}b up jexec alcatraz sysctl net.link.vlan.mtag_pcp=1 jexec alcatraz dhclient ${vlan} & atf_check -s exit:1 -o ignore -e ignore $(atf_get_srcdir)/pcp.py \ --expect-pcp 6 \ --recvif ${epair}a jexec alcatraz killall dhclient sleep 1 jexec alcatraz dhclient -c $(atf_get_srcdir)/dhclient_pcp.conf ${vlan} & atf_check -s exit:0 -o ignore -e ignore $(atf_get_srcdir)/pcp.py \ --expect-pcp 6 \ --recvif ${epair}a } bpf_pcp_cleanup() { sysctl net.link.vlan.mtag_pcp=0 jexec alcatraz killall dhclient vnet_cleanup } atf_init_test_cases() { atf_add_test_case "basic" atf_add_test_case "qinq_simple" atf_add_test_case "qinq_deep" atf_add_test_case "qinq_legacy" atf_add_test_case "qinq_dot" atf_add_test_case "qinq_setflags" atf_add_test_case "bpf_pcp" }