aboutsummaryrefslogtreecommitdiff
path: root/sys/net/if_vlan.c
diff options
context:
space:
mode:
authorBill Paul <wpaul@FreeBSD.org>1999-03-15 01:17:26 +0000
committerBill Paul <wpaul@FreeBSD.org>1999-03-15 01:17:26 +0000
commitf731f104902dba7fa5c765241750a1190bf3a27e (patch)
tree90b8826b8c17798adec9b9a58ac9091fd6504f47 /sys/net/if_vlan.c
parent170f96bb1a4eca160e8b69cc802463bafac394b7 (diff)
downloadsrc-f731f104902dba7fa5c765241750a1190bf3a27e.tar.gz
src-f731f104902dba7fa5c765241750a1190bf3a27e.zip
Updates for vlan stuff:
- add support for devices that do vlan tag insertion/deletion in firmware - add multicast support - add vlan_unconfig() to complement vlan_config() - update ifconfig(8) to configure vlan interfaces (vlan tag and parent device) Also fix a small bug in ifconfig; sometimes sa_family is overwritten by ioctls. Reviewed by: wollman
Notes
Notes: svn path=/head/; revision=44763
Diffstat (limited to 'sys/net/if_vlan.c')
-rw-r--r--sys/net/if_vlan.c253
1 files changed, 228 insertions, 25 deletions
diff --git a/sys/net/if_vlan.c b/sys/net/if_vlan.c
index 2488b31a1a36..3960ee05a924 100644
--- a/sys/net/if_vlan.c
+++ b/sys/net/if_vlan.c
@@ -26,7 +26,7 @@
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
- * $Id: if_vlan.c,v 1.3 1998/08/23 03:07:10 wollman Exp $
+ * $Id: if_vlan.c,v 1.9 1999/03/15 00:33:02 wpaul Exp $
*/
/*
@@ -39,6 +39,18 @@
* ether_output() left on our output queue queue when it calls
* if_start(), rewrite them for use by the real outgoing interface,
* and ask it to send them.
+ *
+ * XXX It's incorrect to assume that we must always kludge up
+ * headers on the physical device's behalf: some devices support
+ * VLAN tag insersion and extraction in firmware. For these cases,
+ * one can change the behavior of the vlan interface by setting
+ * the LINK0 flag on it (that is setting the vlan interface's LINK0
+ * flag, _not_ the parent's LINK0 flag; we try to leave the parent
+ * alone). If the interface as the LINK0 flag set, then it will
+ * not modify the ethernet header on output because the parent
+ * can do that for itself. On input, the parent can call vlan_input_tag()
+ * directly in order to supply us with an incoming mbuf and the vlan
+ * tag value that goes with it.
*/
#include "vlan.h"
@@ -48,7 +60,9 @@
#include <sys/param.h>
#include <sys/kernel.h>
+#include <sys/malloc.h>
#include <sys/mbuf.h>
+#include <sys/queue.h>
#include <sys/socket.h>
#include <sys/sockio.h>
#include <sys/sysctl.h>
@@ -81,6 +95,61 @@ static struct ifvlan ifv_softc[NVLAN];
static void vlan_start(struct ifnet *ifp);
static void vlan_ifinit(void *foo);
static int vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t addr);
+static int vlan_setmulti(struct ifnet *ifp);
+static int vlan_unconfig(struct ifnet *ifp);
+static int vlan_config(struct ifvlan *ifv, struct ifnet *p);
+
+/*
+ * Program our multicast filter. What we're actually doing is
+ * programming the multicast filter of the parent. This has the
+ * side effect of causing the parent interface to receive multicast
+ * traffic that it doesn't really want, which ends up being discarded
+ * later by the upper protocol layers. Unfortunately, there's no way
+ * to avoid this: there really is only one physical interface.
+ */
+static int vlan_setmulti(struct ifnet *ifp)
+{
+ struct ifnet *ifp_p;
+ struct ifmultiaddr *ifma, *rifma = NULL;
+ struct ifvlan *sc;
+ struct vlan_mc_entry *mc = NULL;
+ struct sockaddr_dl sdl;
+ int error;
+
+ /* Find the parent. */
+ sc = ifp->if_softc;
+ ifp_p = sc->ifv_p;
+
+ sdl.sdl_len = ETHER_ADDR_LEN;
+ sdl.sdl_family = AF_LINK;
+
+ /* First, remove any existing filter entries. */
+ while(sc->vlan_mc_listhead.slh_first != NULL) {
+ mc = sc->vlan_mc_listhead.slh_first;
+ bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN);
+ error = if_delmulti(ifp_p, (struct sockaddr *)&sdl);
+ if (error)
+ return(error);
+ SLIST_REMOVE_HEAD(&sc->vlan_mc_listhead, mc_entries);
+ free(mc, M_DEVBUF);
+ }
+
+ /* Now program new ones. */
+ for (ifma = ifp->if_multiaddrs.lh_first;
+ ifma != NULL;ifma = ifma->ifma_link.le_next) {
+ if (ifma->ifma_addr->sa_family != AF_LINK)
+ continue;
+ mc = malloc(sizeof(struct vlan_mc_entry), M_DEVBUF, M_NOWAIT);
+ bcopy(LLADDR((struct sockaddr_dl *)ifma->ifma_addr),
+ (char *)&mc->mc_addr, ETHER_ADDR_LEN);
+ SLIST_INSERT_HEAD(&sc->vlan_mc_listhead, mc, mc_entries);
+ error = if_addmulti(ifp_p, (struct sockaddr *)&sdl, &rifma);
+ if (error)
+ return(error);
+ }
+
+ return(0);
+}
static void
vlaninit(void *dummy)
@@ -102,6 +171,7 @@ vlaninit(void *dummy)
ifp->if_start = vlan_start;
ifp->if_ioctl = vlan_ioctl;
ifp->if_output = ether_output;
+ ifp->if_snd.ifq_maxlen = ifqmaxlen;
if_attach(ifp);
ether_ifattach(ifp);
#if NBPFILTER > 0
@@ -118,7 +188,7 @@ PSEUDO_SET(vlaninit, if_vlan);
static void
vlan_ifinit(void *foo)
{
- ;
+ return;
}
static void
@@ -142,22 +212,48 @@ vlan_start(struct ifnet *ifp)
bpf_mtap(ifp, m);
#endif /* NBPFILTER > 0 */
- M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT);
- if (m == 0)
- continue;
- /* M_PREPEND takes care of m_len, m_pkthdr.len for us */
-
/*
- * Transform the Ethernet header into an Ethernet header
- * with 802.1Q encapsulation.
+ * If the LINK0 flag is set, it means the underlying interface
+ * can do VLAN tag insertion itself and doesn't require us to
+ * create a special header for it. In this case, we just pass
+ * the packet along. However, we need some way to tell the
+ * interface where the packet came from so that it knows how
+ * to find the VLAN tag to use, so we set the rcvif in the
+ * mbuf header to our ifnet.
+ *
+ * Note: we also set the M_PROTO1 flag in the mbuf to let
+ * the parent driver know that the rcvif pointer is really
+ * valid. We need to do this because sometimes mbufs will
+ * be allocated by other parts of the system that contain
+ * garbage in the rcvif pointer. Using the M_PROTO1 flag
+ * lets the driver perform a proper sanity check and avoid
+ * following potentially bogus rcvif pointers off into
+ * never-never land.
*/
- bcopy(mtod(m, char *) + EVL_ENCAPLEN, mtod(m, char *),
- sizeof(struct ether_header));
- evl = mtod(m, struct ether_vlan_header *);
- evl->evl_proto = evl->evl_encap_proto;
- evl->evl_encap_proto = htons(vlan_proto);
- evl->evl_tag = htons(ifv->ifv_tag);
- printf("vlan_start: %*D\n", sizeof *evl, (char *)evl, ":");
+ if (ifp->if_flags & IFF_LINK0) {
+ m->m_pkthdr.rcvif = ifp;
+ m->m_flags |= M_PROTO1;
+ } else {
+ M_PREPEND(m, EVL_ENCAPLEN, M_DONTWAIT);
+ if (m == 0)
+ continue;
+ /* M_PREPEND takes care of m_len, m_pkthdr.len for us */
+
+ /*
+ * Transform the Ethernet header into an Ethernet header
+ * with 802.1Q encapsulation.
+ */
+ bcopy(mtod(m, char *) + EVL_ENCAPLEN, mtod(m, char *),
+ sizeof(struct ether_header));
+ evl = mtod(m, struct ether_vlan_header *);
+ evl->evl_proto = evl->evl_encap_proto;
+ evl->evl_encap_proto = htons(vlan_proto);
+ evl->evl_tag = htons(ifv->ifv_tag);
+#ifdef DEBUG
+ printf("vlan_start: %*D\n", sizeof *evl,
+ (char *)evl, ":");
+#endif
+ }
/*
* Send it, precisely as ether_output() would have.
@@ -166,12 +262,64 @@ vlan_start(struct ifnet *ifp)
if (IF_QFULL(&p->if_snd)) {
IF_DROP(&p->if_snd);
/* XXX stats */
+ ifp->if_oerrors++;
+ m_freem(m);
+ continue;
}
IF_ENQUEUE(&p->if_snd, m);
- if ((p->if_flags & IFF_OACTIVE) == 0)
+ if ((p->if_flags & IFF_OACTIVE) == 0) {
p->if_start(p);
+ ifp->if_opackets++;
+ }
}
ifp->if_flags &= ~IFF_OACTIVE;
+
+ return;
+}
+
+void
+vlan_input_tag(struct ether_header *eh, struct mbuf *m, u_int16_t t)
+{
+ int i;
+ struct ifvlan *ifv;
+
+ for (i = 0; i < NVLAN; i++) {
+ ifv = &ifv_softc[i];
+ if (ifv->ifv_tag == t)
+ break;
+ }
+
+ if (i >= NVLAN || (ifv->ifv_if.if_flags & IFF_UP) == 0) {
+ m_freem(m);
+ ifv->ifv_p->if_data.ifi_noproto++;
+ return;
+ }
+
+ /*
+ * Having found a valid vlan interface corresponding to
+ * the given source interface and vlan tag, run the
+ * the real packet through ethert_input().
+ */
+ m->m_pkthdr.rcvif = &ifv->ifv_if;
+
+#if NBPFILTER > 0
+ if (ifv->ifv_if.if_bpf) {
+ /*
+ * Do the usual BPF fakery. Note that we don't support
+ * promiscuous mode here, since it would require the
+ * drivers to know about VLANs and we're not ready for
+ * that yet.
+ */
+ struct mbuf m0;
+ m0.m_next = m;
+ m0.m_len = sizeof(struct ether_header);
+ m0.m_data = (char *)eh;
+ bpf_mtap(&ifv->ifv_if, &m0);
+ }
+#endif
+ ifv->ifv_if.if_ipackets++;
+ ether_input(&ifv->ifv_if, eh, m);
+ return;
}
int
@@ -221,6 +369,7 @@ vlan_input(struct ether_header *eh, struct mbuf *m)
bpf_mtap(&ifv->ifv_if, &m0);
}
#endif
+ ifv->ifv_if.if_ipackets++;
ether_input(&ifv->ifv_if, eh, m);
return 0;
}
@@ -242,9 +391,9 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p)
ifv->ifv_if.if_mtu = p->if_data.ifi_mtu - EVL_ENCAPLEN;
/*
- * NB: we don't support multicast at this point.
+ * Preserve the state of the LINK0 flag for ourselves.
*/
- ifv->ifv_if.if_flags = (p->if_flags & ~IFF_MULTICAST); /* XXX */
+ ifv->ifv_if.if_flags = (p->if_flags & ~(IFF_LINK0));
/*
* Set up our ``Ethernet address'' to reflect the underlying
@@ -262,6 +411,55 @@ vlan_config(struct ifvlan *ifv, struct ifnet *p)
}
static int
+vlan_unconfig(struct ifnet *ifp)
+{
+ struct ifaddr *ifa;
+ struct sockaddr_dl *sdl;
+ struct vlan_mc_entry *mc;
+ struct ifvlan *ifv;
+ struct ifnet *p;
+ int error;
+
+ ifv = ifp->if_softc;
+ p = ifv->ifv_p;
+
+ /*
+ * Since the interface is being unconfigured, we need to
+ * empty the list of multicast groups that we may have joined
+ * while we were alive and remove them from the parent's list
+ * as well.
+ */
+ while(ifv->vlan_mc_listhead.slh_first != NULL) {
+ struct sockaddr_dl sdl;
+
+ sdl.sdl_len = ETHER_ADDR_LEN;
+ sdl.sdl_family = AF_LINK;
+ mc = ifv->vlan_mc_listhead.slh_first;
+ bcopy((char *)&mc->mc_addr, LLADDR(&sdl), ETHER_ADDR_LEN);
+ error = if_delmulti(p, (struct sockaddr *)&sdl);
+ error = if_delmulti(ifp, (struct sockaddr *)&sdl);
+ if (error)
+ return(error);
+ SLIST_REMOVE_HEAD(&ifv->vlan_mc_listhead, mc_entries);
+ free(mc, M_DEVBUF);
+ }
+
+ /* Disconnect from parent. */
+ ifv->ifv_p = NULL;
+ ifv->ifv_if.if_mtu = ETHERMTU;
+
+ /* Clear our MAC address. */
+ ifa = ifnet_addrs[ifv->ifv_if.if_index - 1];
+ sdl = (struct sockaddr_dl *)ifa->ifa_addr;
+ sdl->sdl_type = IFT_ETHER;
+ sdl->sdl_alen = ETHER_ADDR_LEN;
+ bzero(LLADDR(sdl), ETHER_ADDR_LEN);
+ bzero(ifv->ifv_ac.ac_enaddr, ETHER_ADDR_LEN);
+
+ return 0;
+}
+
+static int
vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
{
struct ifaddr *ifa;
@@ -303,6 +501,8 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
case SIOCSIFMTU:
/*
* Set the interface MTU.
+ * This is bogus. The underlying interface might support
+ * jumbo frames.
*/
if (ifr->ifr_mtu > ETHERMTU) {
error = EINVAL;
@@ -316,8 +516,9 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
if (error)
break;
if (vlr.vlr_parent[0] == '\0') {
- ifv->ifv_p = 0;
+ vlan_unconfig(ifp);
if_down(ifp);
+ ifp->if_flags = 0;
break;
}
p = ifunit(vlr.vlr_parent);
@@ -343,17 +544,19 @@ vlan_ioctl(struct ifnet *ifp, u_long cmd, caddr_t data)
case SIOCSIFFLAGS:
/*
- * We don't support all-multicast or promiscuous modes
+ * We don't support promiscuous mode
* right now because it would require help from the
* underlying drivers, which hasn't been implemented.
*/
- if (ifr->ifr_flags & (IFF_PROMISC|IFF_ALLMULTI)) {
- ifp->if_flags &= ~(IFF_PROMISC|IFF_ALLMULTI);
+ if (ifr->ifr_flags & (IFF_PROMISC)) {
+ ifp->if_flags &= ~(IFF_PROMISC);
error = EINVAL;
}
break;
-
- /* NB: this will reject multicast state changes */
+ case SIOCADDMULTI:
+ case SIOCDELMULTI:
+ error = vlan_setmulti(ifp);
+ break;
default:
error = EINVAL;
}