root/sys/rump/net/lib/libvirtif/if_virt.c
/*      $NetBSD: if_virt.c,v 1.59 2021/06/16 00:21:20 riastradh 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.59 2021/06/16 00:21:20 riastradh 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;

        if_initialize(ifp);
        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;
        }
        snprintf(sc->sc_linkstr, LINKSTRNUMLEN, "%d", sc->sc_num);
        error = virtif_create(ifp);
        if (error) {
fail:
                if_detach(ifp);
                if (sc->sc_linkstr != NULL)
                        kmem_free(sc->sc_linkstr, LINKSTRNUMLEN);
#undef LINKSTRNUMLEN
                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);
                if_statinc(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;
}