Add support for stacked VLANs (IEEE 802.1ad, AKA Q-in-Q).

802.1ad interfaces are created with ifconfig using the "vlanproto" parameter.
Eg., the following creates a 802.1Q VLAN (id #42) over a 802.1ad S-VLAN
(id #5) over a physical Ethernet interface (em0).

ifconfig vlan5 create vlandev em0 vlan 5 vlanproto 802.1ad up
ifconfig vlan42 create vlandev vlan5 vlan 42 inet 10.5.42.1/24

VLAN_MTU, VLAN_HWCSUM and VLAN_TSO capabilities should be properly
supported. VLAN_HWTAGGING is only partially supported, as there is
currently no IFCAP_VLAN_* denoting the possibility to set the VLAN
EtherType to anything else than 0x8100 (802.1ad uses 0x88A8).

Submitted by:	Olivier Piras
Sponsored by:	RG Nets
Differential Revision:	https://reviews.freebsd.org/D26436
This commit is contained in:
Alexander V. Chernikov 2020-10-21 21:28:20 +00:00
parent 37d411338e
commit c7cffd65c5
12 changed files with 395 additions and 74 deletions

View File

@ -49,6 +49,11 @@ static const char rcsid[] =
#include "ifconfig.h" #include "ifconfig.h"
typedef enum {
MT_PREFIX,
MT_FILTER,
} clone_match_type;
static void static void
list_cloners(void) list_cloners(void)
{ {
@ -76,7 +81,11 @@ list_cloners(void)
} }
struct clone_defcb { struct clone_defcb {
char ifprefix[IFNAMSIZ]; union {
char ifprefix[IFNAMSIZ];
clone_match_func *ifmatch;
};
clone_match_type clone_mt;
clone_callback_func *clone_cb; clone_callback_func *clone_cb;
SLIST_ENTRY(clone_defcb) next; SLIST_ENTRY(clone_defcb) next;
}; };
@ -85,12 +94,25 @@ static SLIST_HEAD(, clone_defcb) clone_defcbh =
SLIST_HEAD_INITIALIZER(clone_defcbh); SLIST_HEAD_INITIALIZER(clone_defcbh);
void void
clone_setdefcallback(const char *ifprefix, clone_callback_func *p) clone_setdefcallback_prefix(const char *ifprefix, clone_callback_func *p)
{ {
struct clone_defcb *dcp; struct clone_defcb *dcp;
dcp = malloc(sizeof(*dcp)); dcp = malloc(sizeof(*dcp));
strlcpy(dcp->ifprefix, ifprefix, IFNAMSIZ-1); strlcpy(dcp->ifprefix, ifprefix, IFNAMSIZ-1);
dcp->clone_mt = MT_PREFIX;
dcp->clone_cb = p;
SLIST_INSERT_HEAD(&clone_defcbh, dcp, next);
}
void
clone_setdefcallback_filter(clone_match_func *filter, clone_callback_func *p)
{
struct clone_defcb *dcp;
dcp = malloc(sizeof(*dcp));
dcp->ifmatch = filter;
dcp->clone_mt = MT_FILTER;
dcp->clone_cb = p; dcp->clone_cb = p;
SLIST_INSERT_HEAD(&clone_defcbh, dcp, next); SLIST_INSERT_HEAD(&clone_defcbh, dcp, next);
} }
@ -114,8 +136,14 @@ ifclonecreate(int s, void *arg)
if (clone_cb == NULL) { if (clone_cb == NULL) {
/* Try to find a default callback */ /* Try to find a default callback */
SLIST_FOREACH(dcp, &clone_defcbh, next) { SLIST_FOREACH(dcp, &clone_defcbh, next) {
if (strncmp(dcp->ifprefix, ifr.ifr_name, if ((dcp->clone_mt == MT_PREFIX) &&
strlen(dcp->ifprefix)) == 0) { (strncmp(dcp->ifprefix, ifr.ifr_name,
strlen(dcp->ifprefix)) == 0)) {
clone_cb = dcp->clone_cb;
break;
}
if ((dcp->clone_mt == MT_FILTER) &&
dcp->ifmatch(ifr.ifr_name)) {
clone_cb = dcp->clone_cb; clone_cb = dcp->clone_cb;
break; break;
} }

View File

@ -582,7 +582,7 @@ they support in their capabilities.
is a synonym for enabling all available WOL mechanisms. is a synonym for enabling all available WOL mechanisms.
To disable WOL use To disable WOL use
.Fl wol . .Fl wol .
.It Cm vlanmtu , vlanhwtag, vlanhwfilter, vlanhwcsum, vlanhwtso .It Cm vlanmtu , vlanhwtag , vlanhwfilter , vlanhwcsum , vlanhwtso
If the driver offers user-configurable VLAN support, enable If the driver offers user-configurable VLAN support, enable
reception of extended frames, tag processing in hardware, reception of extended frames, tag processing in hardware,
frame filtering in hardware, checksum offloading, or TSO on VLAN, frame filtering in hardware, checksum offloading, or TSO on VLAN,
@ -592,7 +592,7 @@ Note that this must be configured on a physical interface associated with
not on a not on a
.Xr vlan 4 .Xr vlan 4
interface itself. interface itself.
.It Fl vlanmtu , vlanhwtag , vlanhwfilter , vlanhwtso .It Fl vlanmtu , vlanhwtag, vlanhwfilter, vlanhwtso
If the driver offers user-configurable VLAN support, disable If the driver offers user-configurable VLAN support, disable
reception of extended frames, tag processing in hardware, reception of extended frames, tag processing in hardware,
frame filtering in hardware, or TSO on VLAN, frame filtering in hardware, or TSO on VLAN,
@ -2696,7 +2696,7 @@ interfaces:
Set the VLAN tag value to Set the VLAN tag value to
.Ar vlan_tag . .Ar vlan_tag .
This value is a 12-bit VLAN Identifier (VID) which is used to create an 802.1Q This value is a 12-bit VLAN Identifier (VID) which is used to create an 802.1Q
VLAN header for packets sent from the or 802.1ad VLAN header for packets sent from the
.Xr vlan 4 .Xr vlan 4
interface. interface.
Note that Note that
@ -2704,6 +2704,15 @@ Note that
and and
.Cm vlandev .Cm vlandev
must both be set at the same time. must both be set at the same time.
.It Cm vlanproto Ar vlan_proto
Set the VLAN encapsulation protocol to
.Ar vlan_proto .
Supported encapsulation protocols are currently
.Dq 802.1Q
and
.Dq 802.1ad .
The default encapsulation protocol is
.Dq 802.1Q .
.It Cm vlanpcp Ar priority_code_point .It Cm vlanpcp Ar priority_code_point
Priority code point Priority code point
.Pq Dv PCP .Pq Dv PCP

View File

@ -145,8 +145,10 @@ void printb(const char *s, unsigned value, const char *bits);
void ifmaybeload(const char *name); void ifmaybeload(const char *name);
typedef int clone_match_func(const char *);
typedef void clone_callback_func(int, struct ifreq *); typedef void clone_callback_func(int, struct ifreq *);
void clone_setdefcallback(const char *, clone_callback_func *); void clone_setdefcallback_prefix(const char *, clone_callback_func *);
void clone_setdefcallback_filter(clone_match_func *, clone_callback_func *);
void sfp_status(int s, struct ifreq *ifr, int verbose); void sfp_status(int s, struct ifreq *ifr, int verbose);

View File

@ -6069,5 +6069,5 @@ ieee80211_ctor(void)
for (i = 0; i < nitems(ieee80211_cmds); i++) for (i = 0; i < nitems(ieee80211_cmds); i++)
cmd_register(&ieee80211_cmds[i]); cmd_register(&ieee80211_cmds[i]);
af_register(&af_ieee80211); af_register(&af_ieee80211);
clone_setdefcallback("wlan", wlan_create); clone_setdefcallback_prefix("wlan", wlan_create);
} }

View File

@ -66,8 +66,12 @@ static const char rcsid[] =
#define NOTAG ((u_short) -1) #define NOTAG ((u_short) -1)
static const char proto_8021Q[] = "802.1q";
static const char proto_8021ad[] = "802.1ad";
static struct vlanreq params = { static struct vlanreq params = {
.vlr_tag = NOTAG, .vlr_tag = NOTAG,
.vlr_proto = ETHERTYPE_VLAN,
}; };
static int static int
@ -87,6 +91,17 @@ vlan_status(int s)
if (getvlan(s, &ifr, &vreq) == -1) if (getvlan(s, &ifr, &vreq) == -1)
return; return;
printf("\tvlan: %d", vreq.vlr_tag); 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(s, SIOCGVLANPCP, (caddr_t)&ifr) != -1) if (ioctl(s, SIOCGVLANPCP, (caddr_t)&ifr) != -1)
printf(" vlanpcp: %u", ifr.ifr_vlan_pcp); printf(" vlanpcp: %u", ifr.ifr_vlan_pcp);
printf(" parent interface: %s", vreq.vlr_parent[0] == '\0' ? printf(" parent interface: %s", vreq.vlr_parent[0] == '\0' ?
@ -94,9 +109,49 @@ vlan_status(int s)
printf("\n"); 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;
int vid;
strlcpy(ifname, name, IFNAMSIZ);
if ((cp = strrchr(ifname, '.')) == NULL)
return;
/*
* Don't mix vlan/vlandev parameters with dot notation.
*/
if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0')
errx(1, "ambiguous vlan specification");
/*
* 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 ((*cp != '\0') || (vid & ~0xFFF))
errx(1, "invalid vlan tag");
strlcpy(params.vlr_parent, ifname, IFNAMSIZ);
params.vlr_tag = (vid & 0xFFF);
}
static void static void
vlan_create(int s, struct ifreq *ifr) vlan_create(int s, struct ifreq *ifr)
{ {
vlan_parse_ethervid(ifr->ifr_name);
if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0') { if (params.vlr_tag != NOTAG || params.vlr_parent[0] != '\0') {
/* /*
* One or both parameters were specified, make sure both. * One or both parameters were specified, make sure both.
@ -158,6 +213,24 @@ DECL_CMD_FUNC(setvlandev, val, d)
vlan_set(s, &ifr); vlan_set(s, &ifr);
} }
static
DECL_CMD_FUNC(setvlanproto, val, d)
{
struct vlanreq 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) {
params.vlr_proto = ETHERTYPE_QINQ;
} else
errx(1, "invalid value for vlanproto");
if (getvlan(s, &ifr, &vreq) != -1)
vlan_set(s, &ifr);
}
static static
DECL_CMD_FUNC(setvlanpcp, val, d) DECL_CMD_FUNC(setvlanpcp, val, d)
{ {
@ -195,10 +268,12 @@ DECL_CMD_FUNC(unsetvlandev, val, d)
static struct cmd vlan_cmds[] = { static struct cmd vlan_cmds[] = {
DEF_CLONE_CMD_ARG("vlan", setvlantag), DEF_CLONE_CMD_ARG("vlan", setvlantag),
DEF_CLONE_CMD_ARG("vlandev", setvlandev), DEF_CLONE_CMD_ARG("vlandev", setvlandev),
DEF_CLONE_CMD_ARG("vlanproto", setvlanproto),
DEF_CMD_ARG("vlanpcp", setvlanpcp), DEF_CMD_ARG("vlanpcp", setvlanpcp),
/* NB: non-clone cmds */ /* NB: non-clone cmds */
DEF_CMD_ARG("vlan", setvlantag), DEF_CMD_ARG("vlan", setvlantag),
DEF_CMD_ARG("vlandev", setvlandev), DEF_CMD_ARG("vlandev", setvlandev),
DEF_CMD_ARG("vlanproto", setvlanproto),
/* XXX For compatibility. Should become DEF_CMD() some day. */ /* XXX For compatibility. Should become DEF_CMD() some day. */
DEF_CMD_OPTARG("-vlandev", unsetvlandev), DEF_CMD_OPTARG("-vlandev", unsetvlandev),
DEF_CMD("vlanmtu", IFCAP_VLAN_MTU, setifcap), DEF_CMD("vlanmtu", IFCAP_VLAN_MTU, setifcap),
@ -227,5 +302,6 @@ vlan_ctor(void)
cmd_register(&vlan_cmds[i]); cmd_register(&vlan_cmds[i]);
af_register(&af_vlan); af_register(&af_vlan);
callback_register(vlan_cb, NULL); callback_register(vlan_cb, NULL);
clone_setdefcallback("vlan", vlan_create); clone_setdefcallback_prefix("vlan", vlan_create);
clone_setdefcallback_filter(vlan_match_ethervid, vlan_create);
} }

View File

@ -642,5 +642,5 @@ vxlan_ctor(void)
cmd_register(&vxlan_cmds[i]); cmd_register(&vxlan_cmds[i]);
af_register(&af_vxlan); af_register(&af_vxlan);
callback_register(vxlan_cb, NULL); callback_register(vxlan_cb, NULL);
clone_setdefcallback("vxlan", vxlan_create); clone_setdefcallback_prefix("vxlan", vxlan_create);
} }

View File

@ -428,6 +428,7 @@ struct mbuf;
struct route; struct route;
struct sockaddr; struct sockaddr;
struct bpf_if; struct bpf_if;
struct ether_8021q_tag;
extern uint32_t ether_crc32_le(const uint8_t *, size_t); extern uint32_t ether_crc32_le(const uint8_t *, size_t);
extern uint32_t ether_crc32_be(const uint8_t *, size_t); extern uint32_t ether_crc32_be(const uint8_t *, size_t);
@ -441,11 +442,17 @@ extern int ether_output_frame(struct ifnet *, struct mbuf *);
extern char *ether_sprintf(const u_int8_t *); extern char *ether_sprintf(const u_int8_t *);
void ether_vlan_mtap(struct bpf_if *, struct mbuf *, void ether_vlan_mtap(struct bpf_if *, struct mbuf *,
void *, u_int); void *, u_int);
struct mbuf *ether_vlanencap(struct mbuf *, uint16_t); struct mbuf *ether_vlanencap_proto(struct mbuf *, uint16_t, uint16_t);
bool ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p, bool ether_8021q_frame(struct mbuf **mp, struct ifnet *ife,
uint16_t vid, uint8_t pcp); struct ifnet *p, struct ether_8021q_tag *);
void ether_gen_addr(struct ifnet *ifp, struct ether_addr *hwaddr); void ether_gen_addr(struct ifnet *ifp, struct ether_addr *hwaddr);
static __inline struct mbuf *ether_vlanencap(struct mbuf *m, uint16_t tag)
{
return ether_vlanencap_proto(m, tag, ETHERTYPE_VLAN);
}
/* new ethernet interface attached event */ /* new ethernet interface attached event */
typedef void (*ether_ifattach_event_handler_t)(void *, struct ifnet *); typedef void (*ether_ifattach_event_handler_t)(void *, struct ifnet *);
EVENTHANDLER_DECLARE(ether_ifattach_event, ether_ifattach_event_handler_t); EVENTHANDLER_DECLARE(ether_ifattach_event, ether_ifattach_event_handler_t);

View File

@ -571,7 +571,7 @@ if_clone_addgroup(struct ifnet *ifp, struct if_clone *ifc)
/* /*
* A utility function to extract unit numbers from interface names of * A utility function to extract unit numbers from interface names of
* the form name###. * the form name###[.###].
* *
* Returns 0 on success and an error on failure. * Returns 0 on success and an error on failure.
*/ */
@ -582,7 +582,9 @@ ifc_name2unit(const char *name, int *unit)
int cutoff = INT_MAX / 10; int cutoff = INT_MAX / 10;
int cutlim = INT_MAX % 10; int cutlim = INT_MAX % 10;
for (cp = name; *cp != '\0' && (*cp < '0' || *cp > '9'); cp++); if ((cp = strrchr(name, '.')) == NULL)
cp = name;
for (; *cp != '\0' && (*cp < '0' || *cp > '9'); cp++);
if (*cp == '\0') { if (*cp == '\0') {
*unit = -1; *unit = -1;
} else if (cp[0] == '0' && cp[1] != '\0') { } else if (cp[0] == '0' && cp[1] != '\0') {

View File

@ -441,11 +441,18 @@ bad: if (m != NULL)
static bool static bool
ether_set_pcp(struct mbuf **mp, struct ifnet *ifp, uint8_t pcp) ether_set_pcp(struct mbuf **mp, struct ifnet *ifp, uint8_t pcp)
{ {
struct ether_8021q_tag qtag;
struct ether_header *eh; struct ether_header *eh;
eh = mtod(*mp, struct ether_header *); eh = mtod(*mp, struct ether_header *);
if (ntohs(eh->ether_type) == ETHERTYPE_VLAN || if (ntohs(eh->ether_type) == ETHERTYPE_VLAN ||
ether_8021q_frame(mp, ifp, ifp, 0, pcp)) ntohs(eh->ether_type) == ETHERTYPE_QINQ)
return (true);
qtag.vid = 0;
qtag.pcp = pcp;
qtag.proto = ETHERTYPE_VLAN;
if (ether_8021q_frame(mp, ifp, ifp, &qtag))
return (true); return (true);
if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
return (false); return (false);
@ -616,9 +623,9 @@ ether_input_internal(struct ifnet *ifp, struct mbuf *m)
* If the hardware did not process an 802.1Q tag, do this now, * If the hardware did not process an 802.1Q tag, do this now,
* to allow 802.1P priority frames to be passed to the main input * to allow 802.1P priority frames to be passed to the main input
* path correctly. * path correctly.
* TODO: Deal with Q-in-Q frames, but not arbitrary nesting levels.
*/ */
if ((m->m_flags & M_VLANTAG) == 0 && etype == ETHERTYPE_VLAN) { if ((m->m_flags & M_VLANTAG) == 0 &&
((etype == ETHERTYPE_VLAN) || (etype == ETHERTYPE_QINQ))) {
struct ether_vlan_header *evl; struct ether_vlan_header *evl;
if (m->m_len < sizeof(*evl) && if (m->m_len < sizeof(*evl) &&
@ -1303,7 +1310,7 @@ ether_vlan_mtap(struct bpf_if *bp, struct mbuf *m, void *data, u_int dlen)
} }
struct mbuf * struct mbuf *
ether_vlanencap(struct mbuf *m, uint16_t tag) ether_vlanencap_proto(struct mbuf *m, uint16_t tag, uint16_t proto)
{ {
struct ether_vlan_header *evl; struct ether_vlan_header *evl;
@ -1325,7 +1332,7 @@ ether_vlanencap(struct mbuf *m, uint16_t tag)
evl = mtod(m, struct ether_vlan_header *); evl = mtod(m, struct ether_vlan_header *);
bcopy((char *)evl + ETHER_VLAN_ENCAP_LEN, bcopy((char *)evl + ETHER_VLAN_ENCAP_LEN,
(char *)evl, ETHER_HDR_LEN - ETHER_TYPE_LEN); (char *)evl, ETHER_HDR_LEN - ETHER_TYPE_LEN);
evl->evl_encap_proto = htons(ETHERTYPE_VLAN); evl->evl_encap_proto = htons(proto);
evl->evl_tag = htons(tag); evl->evl_tag = htons(tag);
return (m); return (m);
} }
@ -1354,7 +1361,7 @@ SYSCTL_INT(_net_link_vlan, OID_AUTO, mtag_pcp, CTLFLAG_RW,
bool bool
ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p, ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p,
uint16_t vid, uint8_t pcp) struct ether_8021q_tag *qtag)
{ {
struct m_tag *mtag; struct m_tag *mtag;
int n; int n;
@ -1391,7 +1398,7 @@ ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p,
* If PCP is set in mbuf, use it * If PCP is set in mbuf, use it
*/ */
if ((*mp)->m_flags & M_VLANTAG) { if ((*mp)->m_flags & M_VLANTAG) {
pcp = EVL_PRIOFTAG((*mp)->m_pkthdr.ether_vtag); qtag->pcp = EVL_PRIOFTAG((*mp)->m_pkthdr.ether_vtag);
} }
/* /*
@ -1403,14 +1410,15 @@ ether_8021q_frame(struct mbuf **mp, struct ifnet *ife, struct ifnet *p,
*/ */
if (vlan_mtag_pcp && (mtag = m_tag_locate(*mp, MTAG_8021Q, if (vlan_mtag_pcp && (mtag = m_tag_locate(*mp, MTAG_8021Q,
MTAG_8021Q_PCP_OUT, NULL)) != NULL) MTAG_8021Q_PCP_OUT, NULL)) != NULL)
tag = EVL_MAKETAG(vid, *(uint8_t *)(mtag + 1), 0); tag = EVL_MAKETAG(qtag->vid, *(uint8_t *)(mtag + 1), 0);
else else
tag = EVL_MAKETAG(vid, pcp, 0); tag = EVL_MAKETAG(qtag->vid, qtag->pcp, 0);
if (p->if_capenable & IFCAP_VLAN_HWTAGGING) { if ((p->if_capenable & IFCAP_VLAN_HWTAGGING) &&
(qtag->proto == ETHERTYPE_VLAN)) {
(*mp)->m_pkthdr.ether_vtag = tag; (*mp)->m_pkthdr.ether_vtag = tag;
(*mp)->m_flags |= M_VLANTAG; (*mp)->m_flags |= M_VLANTAG;
} else { } else {
*mp = ether_vlanencap(*mp, tag); *mp = ether_vlanencap_proto(*mp, tag, qtag->proto);
if (*mp == NULL) { if (*mp == NULL) {
if_printf(ife, "unable to prepend 802.1Q header"); if_printf(ife, "unable to prepend 802.1Q header");
return (false); return (false);

View File

@ -17,7 +17,7 @@
* no representations about the suitability of this software for any * no representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied * purpose. It is provided "as is" without express or implied
* warranty. * warranty.
* *
* THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS
* ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
@ -185,17 +185,17 @@ struct ifvlan {
struct ifvlantrunk *ifv_trunk; struct ifvlantrunk *ifv_trunk;
struct ifnet *ifv_ifp; struct ifnet *ifv_ifp;
#define TRUNK(ifv) ((ifv)->ifv_trunk) #define TRUNK(ifv) ((ifv)->ifv_trunk)
#define PARENT(ifv) ((ifv)->ifv_trunk->parent) #define PARENT(ifv) (TRUNK(ifv)->parent)
void *ifv_cookie; void *ifv_cookie;
int ifv_pflags; /* special flags we have set on parent */ int ifv_pflags; /* special flags we have set on parent */
int ifv_capenable; int ifv_capenable;
int ifv_encaplen; /* encapsulation length */ int ifv_encaplen; /* encapsulation length */
int ifv_mtufudge; /* MTU fudged by this much */ int ifv_mtufudge; /* MTU fudged by this much */
int ifv_mintu; /* min transmission unit */ int ifv_mintu; /* min transmission unit */
uint16_t ifv_proto; /* encapsulation ethertype */ struct ether_8021q_tag ifv_qtag;
uint16_t ifv_tag; /* tag to apply on packets leaving if */ #define ifv_proto ifv_qtag.proto
uint16_t ifv_vid; /* VLAN ID */ #define ifv_vid ifv_qtag.vid
uint8_t ifv_pcp; /* Priority Code Point (PCP). */ #define ifv_pcp ifv_qtag.pcp
struct task lladdr_task; struct task lladdr_task;
CK_SLIST_HEAD(, vlan_mc_entry) vlan_mc_listhead; CK_SLIST_HEAD(, vlan_mc_entry) vlan_mc_listhead;
#ifndef VLAN_ARRAY #ifndef VLAN_ARRAY
@ -222,9 +222,9 @@ static eventhandler_tag ifdetach_tag;
static eventhandler_tag iflladdr_tag; static eventhandler_tag iflladdr_tag;
/* /*
* if_vlan uses two module-level synchronizations primitives to allow concurrent * if_vlan uses two module-level synchronizations primitives to allow concurrent
* modification of vlan interfaces and (mostly) allow for vlans to be destroyed * modification of vlan interfaces and (mostly) allow for vlans to be destroyed
* while they are being used for tx/rx. To accomplish this in a way that has * while they are being used for tx/rx. To accomplish this in a way that has
* acceptable performance and cooperation with other parts of the network stack * acceptable performance and cooperation with other parts of the network stack
* there is a non-sleepable epoch(9) and an sx(9). * there is a non-sleepable epoch(9) and an sx(9).
* *
@ -244,7 +244,7 @@ static eventhandler_tag iflladdr_tag;
static struct sx _VLAN_SX_ID; static struct sx _VLAN_SX_ID;
#define VLAN_LOCKING_INIT() \ #define VLAN_LOCKING_INIT() \
sx_init(&_VLAN_SX_ID, "vlan_sx") sx_init_flags(&_VLAN_SX_ID, "vlan_sx", SX_RECURSE)
#define VLAN_LOCKING_DESTROY() \ #define VLAN_LOCKING_DESTROY() \
sx_destroy(&_VLAN_SX_ID) sx_destroy(&_VLAN_SX_ID)
@ -306,7 +306,8 @@ static int vlan_output(struct ifnet *ifp, struct mbuf *m,
const struct sockaddr *dst, struct route *ro); const struct sockaddr *dst, struct route *ro);
static void vlan_unconfig(struct ifnet *ifp); static void vlan_unconfig(struct ifnet *ifp);
static void vlan_unconfig_locked(struct ifnet *ifp, int departing); static void vlan_unconfig_locked(struct ifnet *ifp, int departing);
static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag); static int vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t tag,
uint16_t proto);
static void vlan_link_state(struct ifnet *ifp); static void vlan_link_state(struct ifnet *ifp);
static void vlan_capabilities(struct ifvlan *ifv); static void vlan_capabilities(struct ifvlan *ifv);
static void vlan_trunk_capabilities(struct ifnet *ifp); static void vlan_trunk_capabilities(struct ifnet *ifp);
@ -760,7 +761,7 @@ vlan_pcp(struct ifnet *ifp, uint16_t *pcpp)
/* /*
* Return a driver specific cookie for this interface. Synchronization * Return a driver specific cookie for this interface. Synchronization
* with setcookie must be provided by the driver. * with setcookie must be provided by the driver.
*/ */
static void * static void *
vlan_cookie(struct ifnet *ifp) vlan_cookie(struct ifnet *ifp)
@ -810,16 +811,6 @@ vlan_devat(struct ifnet *ifp, uint16_t vid)
return (ifp); return (ifp);
} }
/*
* Recalculate the cached VLAN tag exposed via the MIB.
*/
static void
vlan_tag_recalculate(struct ifvlan *ifv)
{
ifv->ifv_tag = EVL_MAKETAG(ifv->ifv_vid, ifv->ifv_pcp, 0);
}
/* /*
* VLAN support can be loaded as a module. The only place in the * VLAN support can be loaded as a module. The only place in the
* system that's intimately aware of this is ether_input. We hook * system that's intimately aware of this is ether_input. We hook
@ -867,7 +858,7 @@ vlan_modevent(module_t mod, int type, void *data)
#else #else
"hash tables with chaining" "hash tables with chaining"
#endif #endif
"\n"); "\n");
break; break;
case MOD_UNLOAD: case MOD_UNLOAD:
@ -926,7 +917,7 @@ VNET_SYSUNINIT(vnet_vlan_uninit, SI_SUB_INIT_IF, SI_ORDER_ANY,
#endif #endif
/* /*
* Check for <etherif>.<vlan> style interface names. * Check for <etherif>.<vlan>[.<vlan> ...] style interface names.
*/ */
static struct ifnet * static struct ifnet *
vlan_clone_match_ethervid(const char *name, int *vidp) vlan_clone_match_ethervid(const char *name, int *vidp)
@ -937,7 +928,7 @@ vlan_clone_match_ethervid(const char *name, int *vidp)
int vid; int vid;
strlcpy(ifname, name, IFNAMSIZ); strlcpy(ifname, name, IFNAMSIZ);
if ((cp = strchr(ifname, '.')) == NULL) if ((cp = strrchr(ifname, '.')) == NULL)
return (NULL); return (NULL);
*cp = '\0'; *cp = '\0';
if ((ifp = ifunit_ref(ifname)) == NULL) if ((ifp = ifunit_ref(ifname)) == NULL)
@ -990,6 +981,7 @@ vlan_clone_create(struct if_clone *ifc, char *name, size_t len, caddr_t params)
int unit; int unit;
int error; int error;
int vid; int vid;
uint16_t proto;
struct ifvlan *ifv; struct ifvlan *ifv;
struct ifnet *ifp; struct ifnet *ifp;
struct ifnet *p; struct ifnet *p;
@ -998,14 +990,15 @@ vlan_clone_create(struct if_clone *ifc, char *name, size_t len, caddr_t params)
struct vlanreq vlr; struct vlanreq vlr;
static const u_char eaddr[ETHER_ADDR_LEN]; /* 00:00:00:00:00:00 */ static const u_char eaddr[ETHER_ADDR_LEN]; /* 00:00:00:00:00:00 */
proto = ETHERTYPE_VLAN;
/* /*
* There are 3 (ugh) ways to specify the cloned device: * There are two ways to specify the cloned device:
* o pass a parameter block with the clone request. * o pass a parameter block with the clone request.
* o specify parameters in the text of the clone device name
* o specify no parameters and get an unattached device that * o specify no parameters and get an unattached device that
* must be configured separately. * must be configured separately.
* The first technique is preferred; the latter two are * The first technique is preferred; the latter is supported
* supported for backwards compatibility. * for backwards compatibility.
* *
* XXXRW: Note historic use of the word "tag" here. New ioctls may be * XXXRW: Note historic use of the word "tag" here. New ioctls may be
* called for. * called for.
@ -1023,10 +1016,8 @@ vlan_clone_create(struct if_clone *ifc, char *name, size_t len, caddr_t params)
return (error); return (error);
} }
vid = vlr.vlr_tag; vid = vlr.vlr_tag;
proto = vlr.vlr_proto;
wildcard = (unit < 0); wildcard = (unit < 0);
} else if ((p = vlan_clone_match_ethervid(name, &vid)) != NULL) {
unit = -1;
wildcard = 0;
} else { } else {
p = NULL; p = NULL;
error = ifc_name2unit(name, &unit); error = ifc_name2unit(name, &unit);
@ -1092,7 +1083,7 @@ vlan_clone_create(struct if_clone *ifc, char *name, size_t len, caddr_t params)
sdl->sdl_type = IFT_L2VLAN; sdl->sdl_type = IFT_L2VLAN;
if (p != NULL) { if (p != NULL) {
error = vlan_config(ifv, p, vid); error = vlan_config(ifv, p, vid, proto);
if_rele(p); if_rele(p);
if (error != 0) { if (error != 0) {
/* /*
@ -1117,7 +1108,9 @@ static int
vlan_clone_destroy(struct if_clone *ifc, struct ifnet *ifp) vlan_clone_destroy(struct if_clone *ifc, struct ifnet *ifp)
{ {
struct ifvlan *ifv = ifp->if_softc; struct ifvlan *ifv = ifp->if_softc;
int unit = ifp->if_dunit;
if (ifp->if_vlantrunk)
return (EBUSY);
ether_ifdetach(ifp); /* first, remove it from system-wide lists */ ether_ifdetach(ifp); /* first, remove it from system-wide lists */
vlan_unconfig(ifp); /* now it can be unconfigured and freed */ vlan_unconfig(ifp); /* now it can be unconfigured and freed */
@ -1130,7 +1123,7 @@ vlan_clone_destroy(struct if_clone *ifc, struct ifnet *ifp)
NET_EPOCH_WAIT(); NET_EPOCH_WAIT();
if_free(ifp); if_free(ifp);
free(ifv, M_VLAN); free(ifv, M_VLAN);
ifc_free_unit(ifc, unit); ifc_free_unit(ifc, ifp->if_dunit);
return (0); return (0);
} }
@ -1196,7 +1189,7 @@ vlan_transmit(struct ifnet *ifp, struct mbuf *m)
return (ENETDOWN); return (ENETDOWN);
} }
if (!ether_8021q_frame(&m, ifp, p, ifv->ifv_vid, ifv->ifv_pcp)) { if (!ether_8021q_frame(&m, ifp, p, &ifv->ifv_qtag)) {
if_inc_counter(ifp, IFCOUNTER_OERRORS, 1); if_inc_counter(ifp, IFCOUNTER_OERRORS, 1);
return (0); return (0);
} }
@ -1223,12 +1216,19 @@ vlan_output(struct ifnet *ifp, struct mbuf *m, const struct sockaddr *dst,
NET_EPOCH_ASSERT(); NET_EPOCH_ASSERT();
/*
* Find the first non-VLAN parent interface.
*/
ifv = ifp->if_softc; ifv = ifp->if_softc;
if (TRUNK(ifv) == NULL) { do {
m_freem(m); if (TRUNK(ifv) == NULL) {
return (ENETDOWN); m_freem(m);
} return (ENETDOWN);
p = PARENT(ifv); }
p = PARENT(ifv);
ifv = p->if_softc;
} while (p->if_type == IFT_L2VLAN);
return p->if_output(ifp, m, dst, ro); return p->if_output(ifp, m, dst, ro);
} }
@ -1357,7 +1357,8 @@ vlan_lladdr_fn(void *arg, int pending __unused)
} }
static int static int
vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid) vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid,
uint16_t proto)
{ {
struct epoch_tracker et; struct epoch_tracker et;
struct ifvlantrunk *trunk; struct ifvlantrunk *trunk;
@ -1369,6 +1370,7 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid)
* they handle the tagging and headers themselves. * they handle the tagging and headers themselves.
*/ */
if (p->if_type != IFT_ETHER && if (p->if_type != IFT_ETHER &&
p->if_type != IFT_L2VLAN &&
(p->if_capenable & IFCAP_VLAN_HWTAGGING) == 0) (p->if_capenable & IFCAP_VLAN_HWTAGGING) == 0)
return (EPROTONOSUPPORT); return (EPROTONOSUPPORT);
if ((p->if_flags & VLAN_IFFLAGS) != VLAN_IFFLAGS) if ((p->if_flags & VLAN_IFFLAGS) != VLAN_IFFLAGS)
@ -1400,11 +1402,10 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p, uint16_t vid)
ifv->ifv_vid = vid; /* must set this before vlan_inshash() */ ifv->ifv_vid = vid; /* must set this before vlan_inshash() */
ifv->ifv_pcp = 0; /* Default: best effort delivery. */ ifv->ifv_pcp = 0; /* Default: best effort delivery. */
vlan_tag_recalculate(ifv);
error = vlan_inshash(trunk, ifv); error = vlan_inshash(trunk, ifv);
if (error) if (error)
goto done; goto done;
ifv->ifv_proto = ETHERTYPE_VLAN; ifv->ifv_proto = proto;
ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN; ifv->ifv_encaplen = ETHER_VLAN_ENCAP_LEN;
ifv->ifv_mintu = ETHERMIN; ifv->ifv_mintu = ETHERMIN;
ifv->ifv_pflags = 0; ifv->ifv_pflags = 0;
@ -1915,7 +1916,7 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
break; break;
} }
oldmtu = ifp->if_mtu; oldmtu = ifp->if_mtu;
error = vlan_config(ifv, p, vlr.vlr_tag); error = vlan_config(ifv, p, vlr.vlr_tag, vlr.vlr_proto);
if_rele(p); if_rele(p);
/* /*
@ -1943,11 +1944,12 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
strlcpy(vlr.vlr_parent, PARENT(ifv)->if_xname, strlcpy(vlr.vlr_parent, PARENT(ifv)->if_xname,
sizeof(vlr.vlr_parent)); sizeof(vlr.vlr_parent));
vlr.vlr_tag = ifv->ifv_vid; vlr.vlr_tag = ifv->ifv_vid;
vlr.vlr_proto = ifv->ifv_proto;
} }
VLAN_SUNLOCK(); VLAN_SUNLOCK();
error = copyout(&vlr, ifr_data_get_ptr(ifr), sizeof(vlr)); error = copyout(&vlr, ifr_data_get_ptr(ifr), sizeof(vlr));
break; break;
case SIOCSIFFLAGS: case SIOCSIFFLAGS:
/* /*
* We should propagate selected flags to the parent, * We should propagate selected flags to the parent,
@ -2001,7 +2003,6 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
} }
ifv->ifv_pcp = ifr->ifr_vlan_pcp; ifv->ifv_pcp = ifr->ifr_vlan_pcp;
ifp->if_pcp = ifv->ifv_pcp; ifp->if_pcp = ifv->ifv_pcp;
vlan_tag_recalculate(ifv);
/* broadcast event about PCP change */ /* broadcast event about PCP change */
EVENTHANDLER_INVOKE(ifnet_event, ifp, IFNET_EVENT_PCP); EVENTHANDLER_INVOKE(ifnet_event, ifp, IFNET_EVENT_PCP);
break; break;

View File

@ -69,6 +69,7 @@
struct vlanreq { struct vlanreq {
char vlr_parent[IFNAMSIZ]; char vlr_parent[IFNAMSIZ];
u_short vlr_tag; u_short vlr_tag;
u_short vlr_proto;
}; };
#define SIOCSETVLAN SIOCSIFGENERIC #define SIOCSETVLAN SIOCSIFGENERIC
#define SIOCGETVLAN SIOCGIFGENERIC #define SIOCGETVLAN SIOCGIFGENERIC
@ -123,6 +124,15 @@ struct vlanreq {
#define MTAG_8021Q_PCP_IN 0 /* Input priority. */ #define MTAG_8021Q_PCP_IN 0 /* Input priority. */
#define MTAG_8021Q_PCP_OUT 1 /* Output priority. */ #define MTAG_8021Q_PCP_OUT 1 /* Output priority. */
/*
* 802.1q full tag. Proto and vid are stored in host byte order.
*/
struct ether_8021q_tag {
uint16_t proto;
uint16_t vid;
uint8_t pcp;
};
#define VLAN_CAPABILITIES(_ifp) do { \ #define VLAN_CAPABILITIES(_ifp) do { \
if ((_ifp)->if_vlantrunk != NULL) \ if ((_ifp)->if_vlantrunk != NULL) \
(*vlan_trunk_cap_p)(_ifp); \ (*vlan_trunk_cap_p)(_ifp); \

View File

@ -36,7 +36,185 @@ basic_cleanup()
vnet_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_init_test_cases() atf_init_test_cases()
{ {
atf_add_test_case "basic" 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"
} }