/* $NetBSD: if_virt.c,v 1.57 2018/06/26 06:48:03 msaitoh Exp $ */
/*
* Copyright (c) 2008, 2013 Antti Kantee. All Rights Reserved.
*
* 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.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 THE AUTHOR OR CONTRIBUTORS 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 <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: if_virt.c,v 1.57 2018/06/26 06:48:03 msaitoh Exp $");
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/kmem.h>
#include <sys/cprng.h>
#include <sys/module.h>
#include <net/bpf.h>
#include <net/if.h>
#include <net/if_dl.h>
#include <net/if_ether.h>
#include <netinet/in.h>
#include <netinet/in_var.h>
#include "if_virt.h"
#include "virtif_user.h"
/*
* Virtual interface. Uses hypercalls to shovel packets back
* and forth. The exact method for shoveling depends on the
* hypercall implementation.
*/
static int virtif_init(struct ifnet *);
static int virtif_ioctl(struct ifnet *, u_long, void *);
static void virtif_start(struct ifnet *);
static void virtif_stop(struct ifnet *, int);
struct virtif_sc {
struct ethercom sc_ec;
struct virtif_user *sc_viu;
int sc_num;
char *sc_linkstr;
};
static int virtif_clone(struct if_clone *, int);
static int virtif_unclone(struct ifnet *);
struct if_clone VIF_CLONER =
IF_CLONE_INITIALIZER(VIF_NAME, virtif_clone, virtif_unclone);
static int
virtif_create(struct ifnet *ifp)
{
uint8_t enaddr[ETHER_ADDR_LEN] = { 0xb2, 0x0a, 0x00, 0x0b, 0x0e, 0x01 };
char enaddrstr[3*ETHER_ADDR_LEN];
struct virtif_sc *sc = ifp->if_softc;
int error;
if (sc->sc_viu)
panic("%s: already created", ifp->if_xname);
enaddr[2] = cprng_fast32() & 0xff;
enaddr[5] = sc->sc_num & 0xff;
if ((error = VIFHYPER_CREATE(sc->sc_linkstr,
sc, enaddr, &sc->sc_viu)) != 0) {
printf("VIFHYPER_CREATE failed: %d\n", error);
return error;
}
ether_ifattach(ifp, enaddr);
ether_snprintf(enaddrstr, sizeof(enaddrstr), enaddr);
aprint_normal_ifnet(ifp, "Ethernet address %s\n", enaddrstr);
IFQ_SET_READY(&ifp->if_snd);
return 0;
}
static int
virtif_clone(struct if_clone *ifc, int num)
{
struct virtif_sc *sc;
struct ifnet *ifp;
int error = 0;
sc = kmem_zalloc(sizeof(*sc), KM_SLEEP);
sc->sc_num = num;
ifp = &sc->sc_ec.ec_if;
if_initname(ifp, VIF_NAME, num);
ifp->if_softc = sc;
ifp->if_flags = IFF_BROADCAST | IFF_SIMPLEX | IFF_MULTICAST;
ifp->if_init = virtif_init;
ifp->if_ioctl = virtif_ioctl;
ifp->if_start = virtif_start;
ifp->if_stop = virtif_stop;
ifp->if_mtu = ETHERMTU;
ifp->if_dlt = DLT_EN10MB;
error = if_initialize(ifp);
if (error != 0) {
aprint_error("%s: if_initialize failed(%d)\n", ifp->if_xname,
error);
goto fail_1;
}
if_register(ifp);
#ifndef RUMP_VIF_LINKSTR
/*
* if the underlying interface does not expect linkstr, we can
* create everything now. Otherwise, we need to wait for
* SIOCSLINKSTR.
*/
#define LINKSTRNUMLEN 16
sc->sc_linkstr = kmem_alloc(LINKSTRNUMLEN, KM_SLEEP);
if (sc->sc_linkstr == NULL) {
error = ENOMEM;
goto fail_2;
}
snprintf(sc->sc_linkstr, LINKSTRNUMLEN, "%d", sc->sc_num);
error = virtif_create(ifp);
if (error) {
fail_2:
if_detach(ifp);
if (sc->sc_linkstr != NULL)
kmem_free(sc->sc_linkstr, LINKSTRNUMLEN);
#undef LINKSTRNUMLEN
fail_1:
kmem_free(sc, sizeof(*sc));
ifp->if_softc = NULL;
}
#endif /* !RUMP_VIF_LINKSTR */
return error;
}
static int
virtif_unclone(struct ifnet *ifp)
{
struct virtif_sc *sc = ifp->if_softc;
int rv;
if (ifp->if_flags & IFF_UP)
return EBUSY;
if ((rv = VIFHYPER_DYING(sc->sc_viu)) != 0)
return rv;
virtif_stop(ifp, 1);
if_down(ifp);
VIFHYPER_DESTROY(sc->sc_viu);
kmem_free(sc, sizeof(*sc));
ether_ifdetach(ifp);
if_detach(ifp);
return 0;
}
static int
virtif_init(struct ifnet *ifp)
{
struct virtif_sc *sc = ifp->if_softc;
if (sc->sc_viu == NULL)
return ENXIO;
ifp->if_flags |= IFF_RUNNING;
return 0;
}
static int
virtif_ioctl(struct ifnet *ifp, u_long cmd, void *data)
{
struct virtif_sc *sc = ifp->if_softc;
int rv;
switch (cmd) {
#ifdef RUMP_VIF_LINKSTR
struct ifdrv *ifd;
size_t linkstrlen;
#ifndef RUMP_VIF_LINKSTRMAX
#define RUMP_VIF_LINKSTRMAX 4096
#endif
case SIOCGLINKSTR:
ifd = data;
if (!sc->sc_linkstr) {
rv = ENOENT;
break;
}
linkstrlen = strlen(sc->sc_linkstr)+1;
if (ifd->ifd_cmd == IFLINKSTR_QUERYLEN) {
ifd->ifd_len = linkstrlen;
rv = 0;
break;
}
if (ifd->ifd_cmd != 0) {
rv = ENOTTY;
break;
}
rv = copyoutstr(sc->sc_linkstr,
ifd->ifd_data, MIN(ifd->ifd_len,linkstrlen), NULL);
break;
case SIOCSLINKSTR:
if (ifp->if_flags & IFF_UP) {
rv = EBUSY;
break;
}
ifd = data;
if (ifd->ifd_cmd == IFLINKSTR_UNSET) {
panic("unset linkstr not implemented");
} else if (ifd->ifd_cmd != 0) {
rv = ENOTTY;
break;
} else if (sc->sc_linkstr) {
rv = EBUSY;
break;
}
if (ifd->ifd_len > RUMP_VIF_LINKSTRMAX) {
rv = E2BIG;
break;
} else if (ifd->ifd_len < 1) {
rv = EINVAL;
break;
}
sc->sc_linkstr = kmem_alloc(ifd->ifd_len, KM_SLEEP);
rv = copyinstr(ifd->ifd_data, sc->sc_linkstr,
ifd->ifd_len, NULL);
if (rv) {
kmem_free(sc->sc_linkstr, ifd->ifd_len);
break;
}
rv = virtif_create(ifp);
if (rv) {
kmem_free(sc->sc_linkstr, ifd->ifd_len);
}
break;
#endif /* RUMP_VIF_LINKSTR */
default:
if (!sc->sc_linkstr)
rv = ENXIO;
else
rv = ether_ioctl(ifp, cmd, data);
if (rv == ENETRESET)
rv = 0;
break;
}
return rv;
}
/*
* Output packets in-context until outgoing queue is empty.
* Leave responsibility of choosing whether or not to drop the
* kernel lock to VIPHYPER_SEND().
*/
#define LB_SH 32
static void
virtif_start(struct ifnet *ifp)
{
struct virtif_sc *sc = ifp->if_softc;
struct mbuf *m, *m0;
struct iovec io[LB_SH];
int i;
ifp->if_flags |= IFF_OACTIVE;
for (;;) {
IF_DEQUEUE(&ifp->if_snd, m0);
if (!m0) {
break;
}
m = m0;
for (i = 0; i < LB_SH && m; ) {
if (m->m_len) {
io[i].iov_base = mtod(m, void *);
io[i].iov_len = m->m_len;
i++;
}
m = m->m_next;
}
if (i == LB_SH && m)
panic("lazy bum");
bpf_mtap(ifp, m0, BPF_D_OUT);
VIFHYPER_SEND(sc->sc_viu, io, i);
m_freem(m0);
ifp->if_opackets++;
}
ifp->if_flags &= ~IFF_OACTIVE;
}
static void
virtif_stop(struct ifnet *ifp, int disable)
{
/* XXX: VIFHYPER_STOP() */
ifp->if_flags &= ~IFF_RUNNING;
}
void
VIF_DELIVERPKT(struct virtif_sc *sc, struct iovec *iov, size_t iovlen)
{
struct ifnet *ifp = &sc->sc_ec.ec_if;
struct ether_header *eth;
struct mbuf *m;
size_t i;
int off, olen;
bool passup;
const int align
= ALIGN(sizeof(struct ether_header)) - sizeof(struct ether_header);
if ((ifp->if_flags & IFF_RUNNING) == 0)
return;
m = m_gethdr(M_NOWAIT, MT_DATA);
if (m == NULL)
return; /* drop packet */
m->m_len = m->m_pkthdr.len = 0;
for (i = 0, off = align; i < iovlen; i++) {
olen = m->m_pkthdr.len;
m_copyback(m, off, iov[i].iov_len, iov[i].iov_base);
off += iov[i].iov_len;
if (olen + off != m->m_pkthdr.len) {
aprint_verbose_ifnet(ifp, "m_copyback failed\n");
m_freem(m);
return;
}
}
m->m_data += align;
m->m_pkthdr.len -= align;
m->m_len -= align;
eth = mtod(m, struct ether_header *);
if (memcmp(eth->ether_dhost, CLLADDR(ifp->if_sadl),
ETHER_ADDR_LEN) == 0) {
passup = true;
} else if (ETHER_IS_MULTICAST(eth->ether_dhost)) {
passup = true;
} else if (ifp->if_flags & IFF_PROMISC) {
m->m_flags |= M_PROMISC;
passup = true;
} else {
passup = false;
}
if (passup) {
int bound;
m_set_rcvif(m, ifp);
KERNEL_LOCK(1, NULL);
/* Prevent LWP migrations between CPUs for psref(9) */
bound = curlwp_bind();
if_input(ifp, m);
curlwp_bindx(bound);
KERNEL_UNLOCK_LAST(NULL);
} else {
m_freem(m);
}
m = NULL;
}
/*
* The following ensures that no two modules using if_virt end up with
* the same module name. MODULE() and modcmd wrapped in ... bad mojo.
*/
#define VIF_MOJO(x) MODULE(MODULE_CLASS_DRIVER,x,NULL);
#define VIF_MODULE() VIF_MOJO(VIF_BASENAME(if_virt_,VIRTIF_BASE))
#define VIF_MODCMD VIF_BASENAME3(if_virt_,VIRTIF_BASE,_modcmd)
VIF_MODULE();
static int
VIF_MODCMD(modcmd_t cmd, void *opaque)
{
int error = 0;
switch (cmd) {
case MODULE_CMD_INIT:
if_clone_attach(&VIF_CLONER);
break;
case MODULE_CMD_FINI:
/*
* not sure if interfaces are refcounted
* and properly protected
*/
#if 0
if_clone_detach(&VIF_CLONER);
#else
error = ENOTTY;
#endif
break;
default:
error = ENOTTY;
}
return error;
}