root/usr/src/lib/varpd/libvarpd/common/libvarpd_arp.c
/*
 * This file and its contents are supplied under the terms of the
 * Common Development and Distribution License ("CDDL"), version 1.0.
 * You may only use this file in accordance with the terms of version
 * 1.0 of the CDDL.
 *
 * A full copy of the text of the CDDL should have accompanied this
 * source.  A copy of the CDDL is also available via the Internet at
 * http://www.illumos.org/license/CDDL.
 */

/*
 * Copyright 2015 Joyent, Inc.
 */

/*
 * Common routines for implementing proxy arp
 */

#include <sys/types.h>
#include <net/if.h>
#include <netinet/if_ether.h>
#include <netinet/ip.h>
#include <netinet/ip6.h>
#include <netinet/icmp6.h>
#include <netinet/udp.h>
#include <netinet/dhcp.h>
#include <libvarpd_impl.h>
#include <sys/vlan.h>
#include <strings.h>
#include <assert.h>

#define IPV6_VERSION    6

typedef struct varpd_arp_query {
        int                             vaq_type;
        char                            vaq_buf[ETHERMAX + VLAN_TAGSZ];
        size_t                          vaq_bsize;
        uint8_t                         vaq_lookup[ETHERADDRL];
        struct sockaddr_storage         vaq_sock;
        varpd_instance_t                *vaq_inst;
        struct ether_arp                *vaq_ea;
        varpd_query_handle_t            *vaq_query;
        const overlay_targ_lookup_t     *vaq_otl;
        ip6_t                           *vaq_ip6;
        nd_neighbor_solicit_t           *vaq_ns;
} varpd_arp_query_t;

typedef struct varpd_dhcp_query {
        char                            vdq_buf[ETHERMAX + VLAN_TAGSZ];
        size_t                          vdq_bsize;
        uint8_t                         vdq_lookup[ETHERADDRL];
        const overlay_targ_lookup_t     *vdq_otl;
        varpd_instance_t                *vdq_inst;
        varpd_query_handle_t            *vdq_query;
        struct ether_header             *vdq_ether;
} varpd_dhcp_query_t;

static const uint8_t libvarpd_arp_bcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff,
            0xff };

void
libvarpd_plugin_proxy_arp(varpd_provider_handle_t *hdl,
    varpd_query_handle_t *vqh, const overlay_targ_lookup_t *otl)
{
        varpd_arp_query_t *vaq;
        varpd_instance_t *inst = (varpd_instance_t *)hdl;
        struct ether_arp *ea;
        struct sockaddr_in *ip;

        vaq = umem_alloc(sizeof (varpd_arp_query_t), UMEM_DEFAULT);
        if (vaq == NULL) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                return;
        }
        vaq->vaq_bsize = sizeof (vaq->vaq_buf);

        if (otl->otl_sap != ETHERTYPE_ARP) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        /*
         * An ARP packet should not be very large because it's definited to only
         * be allowed to have a single entry at a given time. But our data must
         * be at least as large as an ether_arp and our header must be at least
         * as large as a standard ethernet header.
         */
        if (otl->otl_hdrsize + otl->otl_pktsize > vaq->vaq_bsize ||
            otl->otl_pktsize < sizeof (struct ether_arp) ||
            otl->otl_hdrsize < sizeof (struct ether_header)) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (libvarpd_overlay_packet(inst->vri_impl, otl, vaq->vaq_buf,
            &vaq->vaq_bsize) != 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (otl->otl_hdrsize + otl->otl_pktsize < vaq->vaq_bsize) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        ea = (void *)((uintptr_t)vaq->vaq_buf + (uintptr_t)otl->otl_hdrsize);

        /*
         * Make sure it matches something that we know about.
         */
        if (ntohs(ea->ea_hdr.ar_hrd) != ARPHRD_ETHER ||
            ntohs(ea->ea_hdr.ar_pro) != ETHERTYPE_IP ||
            ea->ea_hdr.ar_hln != ETHERADDRL ||
            ea->ea_hdr.ar_pln != sizeof (ea->arp_spa) ||
            ntohs(ea->ea_hdr.ar_op) != ARPOP_REQUEST) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        /*
         * Now that we've verified that our data is sane, see if we're doing a
         * gratuitous arp and if so, drop it. Otherwise, we may end up
         * triggering duplicate address detection.
         */
        if (bcmp(ea->arp_spa, ea->arp_tpa, sizeof (ea->arp_spa)) == 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        bzero(&vaq->vaq_sock, sizeof (struct sockaddr_storage));
        ip = (struct sockaddr_in *)&vaq->vaq_sock;
        ip->sin_family = AF_INET;
        bcopy(ea->arp_tpa, &ip->sin_addr, sizeof (ea->arp_tpa));

        vaq->vaq_type = AF_INET;
        vaq->vaq_inst = inst;
        vaq->vaq_ea = ea;
        vaq->vaq_query = vqh;
        vaq->vaq_otl = otl;

        if (inst->vri_plugin->vpp_ops->vpo_arp == NULL)
                libvarpd_panic("%s plugin asked to do arp, but has no method",
                    inst->vri_plugin->vpp_name);

        inst->vri_plugin->vpp_ops->vpo_arp(inst->vri_private,
            (varpd_arp_handle_t *)vaq, VARPD_QTYPE_ETHERNET,
            (struct sockaddr *)ip, vaq->vaq_lookup);
}

static void
libvarpd_proxy_arp_fini(varpd_arp_query_t *vaq)
{
        struct ether_header *ether;
        struct sockaddr_in *ip;

        ip = (struct sockaddr_in *)&vaq->vaq_sock;
        /*
         * Modify our packet in place for a reply. We need to swap around the
         * sender and target addresses.
         */
        vaq->vaq_ea->ea_hdr.ar_op = htons(ARPOP_REPLY);
        bcopy(vaq->vaq_ea->arp_sha, vaq->vaq_ea->arp_tha, ETHERADDRL);
        bcopy(vaq->vaq_lookup, vaq->vaq_ea->arp_sha, ETHERADDRL);
        bcopy(vaq->vaq_ea->arp_spa, &ip->sin_addr,
            sizeof (vaq->vaq_ea->arp_spa));
        bcopy(vaq->vaq_ea->arp_tpa, vaq->vaq_ea->arp_spa,
            sizeof (vaq->vaq_ea->arp_spa));
        bcopy(&ip->sin_addr, vaq->vaq_ea->arp_tpa,
            sizeof (vaq->vaq_ea->arp_spa));

        /*
         * Finally go ahead and fix up the mac header and reply to the sender
         * explicitly.
         */
        ether = (struct ether_header *)vaq->vaq_buf;
        bcopy(&ether->ether_shost, &ether->ether_dhost, ETHERADDRL);
        bcopy(vaq->vaq_lookup, &ether->ether_shost, ETHERADDRL);

        (void) libvarpd_overlay_inject(vaq->vaq_inst->vri_impl, vaq->vaq_otl,
            vaq->vaq_buf, vaq->vaq_bsize);

        libvarpd_plugin_query_reply(vaq->vaq_query, VARPD_LOOKUP_DROP);
        umem_free(vaq, sizeof (varpd_arp_query_t));
}

static uint16_t
libvarpd_icmpv6_checksum(const ip6_t *v6hdr, const uint16_t *buf, uint16_t mlen)
{
        int i;
        uint16_t *v;
        uint32_t sum = 0;

        assert(mlen % 2 == 0);
        v = (uint16_t *)&v6hdr->ip6_src;
        for (i = 0; i < sizeof (struct in6_addr); i += 2, v++)
                sum += *v;
        v = (uint16_t *)&v6hdr->ip6_dst;
        for (i = 0; i < sizeof (struct in6_addr); i += 2, v++)
                sum += *v;
        sum += htons(mlen);
#ifdef _BIG_ENDIAN
        sum += IPPROTO_ICMPV6;
#else
        sum += IPPROTO_ICMPV6 << 8;
#endif  /* _BIG_ENDIAN */

        for (i = 0; i < mlen; i += 2, buf++)
                sum += *buf;

        while ((sum >> 16) != 0)
                sum = (sum & 0xffff) + (sum >> 16);

        return (sum & 0xffff);
}

/*
 * Proxying NDP is much more involved than proxying ARP. For starters, NDP
 * neighbor solicitations are implemented in terms of IPv6 ICMP as opposed to
 * its own Ethertype. Therefore, we're going to have to grab a packet if it's a
 * multicast packet and then determine if we actually want to do anything with
 * it.
 */
void
libvarpd_plugin_proxy_ndp(varpd_provider_handle_t *hdl,
    varpd_query_handle_t *vqh, const overlay_targ_lookup_t *otl)
{
        size_t bsize, plen;
        varpd_arp_query_t *vaq;
        ip6_t *v6hdr;
        nd_neighbor_solicit_t *ns;
        nd_opt_hdr_t *opt;
        struct sockaddr_in6 *s6;

        varpd_instance_t *inst = (varpd_instance_t *)hdl;
        uint8_t *eth = NULL;

        vaq = umem_alloc(sizeof (varpd_arp_query_t), UMEM_DEFAULT);
        if (vaq == NULL) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                return;
        }
        vaq->vaq_bsize = sizeof (vaq->vaq_buf);

        if (otl->otl_dstaddr[0] != 0x33 ||
            otl->otl_dstaddr[1] != 0x33) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        /*
         * If we have more than a standard frame size for the ICMP neighbor
         * solicitation, drop it. Similarly if there isn't enough data present
         * for us, drop it.
         */
        if (otl->otl_hdrsize + otl->otl_pktsize > vaq->vaq_bsize) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (otl->otl_pktsize < sizeof (ip6_t) +
            sizeof (nd_neighbor_solicit_t)) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (libvarpd_overlay_packet(inst->vri_impl, otl, vaq->vaq_buf,
            &vaq->vaq_bsize) != 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        bsize = vaq->vaq_bsize;
        bsize -= otl->otl_hdrsize;
        assert(bsize > sizeof (ip6_t));

        v6hdr = (ip6_t *)(vaq->vaq_buf + otl->otl_hdrsize);
        if (((v6hdr->ip6_vfc & 0xf0) >> 4) != IPV6_VERSION) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (v6hdr->ip6_nxt != IPPROTO_ICMPV6) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        /*
         * In addition to getting these requests on the multicast address for
         * node solicitation, we may also end up getting them on a generic
         * multicast address due to timeouts or other choices by various OSes.
         * We should fairly liberal and accept both, even though the standard
         * wants them to a solicitation address.
         */
        if (!IN6_IS_ADDR_MC_SOLICITEDNODE(&v6hdr->ip6_dst) &&
            !IN6_IS_ADDR_MC_LINKLOCAL(&v6hdr->ip6_dst)) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        bsize -= sizeof (ip6_t);
        plen = ntohs(v6hdr->ip6_plen);
        if (bsize < plen) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        /*
         * Now we know that this is an ICMPv6 request targeting the right
         * IPv6 multicast prefix. Let's go through and verify that ICMPv6
         * indicates that we have the real thing and ensure that per RFC 4861
         * the target address is not a multicast address. Further, because this
         * is a multicast on Ethernet, we must have a source link-layer address.
         *
         * We should probably enforce that we have a valid ICMP checksum at some
         * point.
         */
        ns = (nd_neighbor_solicit_t *)(vaq->vaq_buf + otl->otl_hdrsize +
            sizeof (ip6_t));
        if (ns->nd_ns_type != ND_NEIGHBOR_SOLICIT && ns->nd_ns_code != 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        if (IN6_IS_ADDR_MULTICAST(&ns->nd_ns_target) ||
            IN6_IS_ADDR_V4MAPPED(&ns->nd_ns_target) ||
            IN6_IS_ADDR_LOOPBACK(&ns->nd_ns_target)) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        plen -= sizeof (nd_neighbor_solicit_t);
        opt = (nd_opt_hdr_t *)(ns+1);
        while (plen >= sizeof (struct nd_opt_hdr)) {
                /* If we have an option with no lenght, that's clear bogus */
                if (opt->nd_opt_len == 0) {
                        libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                        umem_free(vaq, sizeof (varpd_arp_query_t));
                        return;
                }

                if (opt->nd_opt_type == ND_OPT_SOURCE_LINKADDR) {
                        eth = (uint8_t *)((uintptr_t)opt +
                            sizeof (nd_opt_hdr_t));
                }
                plen -= opt->nd_opt_len * 8;
                opt = (nd_opt_hdr_t *)((uintptr_t)opt +
                    opt->nd_opt_len * 8);
        }

        if (eth == NULL) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        }

        bzero(&vaq->vaq_sock, sizeof (struct sockaddr_storage));
        s6 = (struct sockaddr_in6 *)&vaq->vaq_sock;
        s6->sin6_family = AF_INET6;
        bcopy(&ns->nd_ns_target, &s6->sin6_addr, sizeof (s6->sin6_addr));

        if (inst->vri_plugin->vpp_ops->vpo_arp == NULL)
                libvarpd_panic("%s plugin asked to do arp, but has no method",
                    inst->vri_plugin->vpp_name);

        vaq->vaq_type = AF_INET6;
        vaq->vaq_inst = inst;
        vaq->vaq_ea = NULL;
        vaq->vaq_query = vqh;
        vaq->vaq_otl = otl;
        vaq->vaq_ns = ns;
        vaq->vaq_ip6 = v6hdr;
        inst->vri_plugin->vpp_ops->vpo_arp(inst->vri_private,
            (varpd_arp_handle_t *)vaq,  VARPD_QTYPE_ETHERNET,
            (struct sockaddr *)s6, vaq->vaq_lookup);
}

static void
libvarpd_proxy_ndp_fini(varpd_arp_query_t *vaq)
{
        char resp[ETHERMAX + VLAN_TAGSZ];
        struct ether_header *ether;
        nd_neighbor_advert_t *na;
        nd_opt_hdr_t *opt;
        ip6_t *v6hdr;
        size_t roff = 0;

        /*
         * Now we need to assemble an RA as a response. Unlike with arp, we opt
         * to use a new packet just to make things a bit simpler saner here.
         */
        v6hdr = vaq->vaq_ip6;
        bcopy(vaq->vaq_buf, resp, vaq->vaq_otl->otl_hdrsize);
        ether = (struct ether_header *)resp;
        bcopy(&ether->ether_shost, &ether->ether_dhost, ETHERADDRL);
        bcopy(vaq->vaq_lookup, &ether->ether_shost, ETHERADDRL);
        roff += vaq->vaq_otl->otl_hdrsize;
        bcopy(v6hdr, resp + roff, sizeof (ip6_t));
        v6hdr = (ip6_t *)(resp + roff);
        bcopy(&v6hdr->ip6_src, &v6hdr->ip6_dst, sizeof (struct in6_addr));
        bcopy(&vaq->vaq_ns->nd_ns_target, &v6hdr->ip6_src,
            sizeof (struct in6_addr));
        roff += sizeof (ip6_t);
        na = (nd_neighbor_advert_t *)(resp + roff);
        na->nd_na_type = ND_NEIGHBOR_ADVERT;
        na->nd_na_code = 0;
        /*
         * RFC 4443 defines that we should set the checksum to zero before we
         * calculate it.
         */
        na->nd_na_cksum = 0;
        /*
         * Nota bene, the header <netinet/icmp6.h> has already transformed this
         * into the appropriate host order. Don't use htonl.
         */
        na->nd_na_flags_reserved = ND_NA_FLAG_SOLICITED | ND_NA_FLAG_OVERRIDE;
        bcopy(&vaq->vaq_ns->nd_ns_target, &na->nd_na_target,
            sizeof (struct in6_addr));
        roff += sizeof (nd_neighbor_advert_t);

        opt = (nd_opt_hdr_t *)(resp + roff);
        opt->nd_opt_type = ND_OPT_TARGET_LINKADDR;
        opt->nd_opt_len = 1;
        roff += sizeof (nd_opt_hdr_t);
        bcopy(vaq->vaq_lookup, resp + roff, ETHERADDRL);
        roff += ETHERADDRL;

        /*
         * Now that we've filled in the packet, go back and compute the checksum
         * and fill in the IPv6 payload size.
         */
        v6hdr->ip6_plen = htons(roff - sizeof (ip6_t) -
            vaq->vaq_otl->otl_hdrsize);
        na->nd_na_cksum = ~libvarpd_icmpv6_checksum(v6hdr, (uint16_t *)na,
            ntohs(v6hdr->ip6_plen)) & 0xffff;

        (void) libvarpd_overlay_inject(vaq->vaq_inst->vri_impl, vaq->vaq_otl,
            resp, roff);

        libvarpd_plugin_query_reply(vaq->vaq_query, VARPD_LOOKUP_DROP);
        umem_free(vaq, sizeof (varpd_arp_query_t));
}

void
libvarpd_plugin_arp_reply(varpd_arp_handle_t *vah, int action)
{
        varpd_arp_query_t *vaq = (varpd_arp_query_t *)vah;

        if (vaq == NULL)
                libvarpd_panic("unknown plugin passed invalid "
                    "varpd_arp_handle_t");

        if (action == VARPD_LOOKUP_DROP) {
                libvarpd_plugin_query_reply(vaq->vaq_query, VARPD_LOOKUP_DROP);
                umem_free(vaq, sizeof (varpd_arp_query_t));
                return;
        } else if (action != VARPD_LOOKUP_OK)
                libvarpd_panic("%s plugin returned invalid action %d",
                    vaq->vaq_inst->vri_plugin->vpp_name, action);

        switch (vaq->vaq_type) {
        case AF_INET:
                libvarpd_proxy_arp_fini(vaq);
                break;
        case AF_INET6:
                libvarpd_proxy_ndp_fini(vaq);
                break;
        default:
                libvarpd_panic("encountered unknown vaq_type: %d",
                    vaq->vaq_type);
        }
}

void
libvarpd_plugin_proxy_dhcp(varpd_provider_handle_t *hdl,
    varpd_query_handle_t *vqh, const overlay_targ_lookup_t *otl)
{
        varpd_dhcp_query_t *vdq;
        struct ether_header *ether;
        struct ip *ip;
        struct udphdr *udp;
        varpd_instance_t *inst = (varpd_instance_t *)hdl;

        vdq = umem_alloc(sizeof (varpd_dhcp_query_t), UMEM_DEFAULT);
        if (vdq == NULL) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                return;
        }
        vdq->vdq_bsize = sizeof (vdq->vdq_buf);

        if (otl->otl_sap != ETHERTYPE_IP) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        if (bcmp(otl->otl_dstaddr, libvarpd_arp_bcast, ETHERADDRL) != 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        if (otl->otl_hdrsize + otl->otl_pktsize > vdq->vdq_bsize ||
            otl->otl_pktsize < sizeof (struct ip) + sizeof (struct udphdr) +
            sizeof (struct dhcp) ||
            otl->otl_hdrsize < sizeof (struct ether_header)) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        if (libvarpd_overlay_packet(inst->vri_impl, otl, vdq->vdq_buf,
            &vdq->vdq_bsize) != 0) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        if (vdq->vdq_bsize != otl->otl_hdrsize + otl->otl_pktsize) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        ether = (struct ether_header *)vdq->vdq_buf;
        ip = (struct ip *)(vdq->vdq_buf + otl->otl_hdrsize);

        if (ip->ip_v != IPVERSION && ip->ip_p != IPPROTO_UDP) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        if (otl->otl_hdrsize + ip->ip_hl * 4 + sizeof (struct udphdr) >
            vdq->vdq_bsize) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        udp = (struct udphdr *)(vdq->vdq_buf + otl->otl_hdrsize +
            ip->ip_hl * 4);

        if (ntohs(udp->uh_sport) != IPPORT_BOOTPC ||
            ntohs(udp->uh_dport) != IPPORT_BOOTPS) {
                libvarpd_plugin_query_reply(vqh, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        }

        vdq->vdq_ether = ether;
        vdq->vdq_inst = inst;
        vdq->vdq_query = vqh;
        vdq->vdq_otl = otl;

        if (inst->vri_plugin->vpp_ops->vpo_dhcp == NULL)
                libvarpd_panic("%s plugin asked to do dhcp, but has no method",
                    inst->vri_plugin->vpp_name);

        inst->vri_plugin->vpp_ops->vpo_dhcp(inst->vri_private,
            (varpd_dhcp_handle_t *)vdq, VARPD_QTYPE_ETHERNET, otl,
            vdq->vdq_lookup);
}

void
libvarpd_plugin_dhcp_reply(varpd_dhcp_handle_t *vdh, int action)
{
        varpd_dhcp_query_t *vdq = (varpd_dhcp_query_t *)vdh;

        if (vdq == NULL)
                libvarpd_panic("unknown plugin passed invalid "
                    "varpd_dhcp_handle_t");

        if (action == VARPD_LOOKUP_DROP) {
                libvarpd_plugin_query_reply(vdq->vdq_query, VARPD_LOOKUP_DROP);
                umem_free(vdq, sizeof (varpd_dhcp_query_t));
                return;
        } else if (action != VARPD_LOOKUP_OK)
                libvarpd_panic("%s plugin returned invalid action %d",
                    vdq->vdq_inst->vri_plugin->vpp_name, action);

        bcopy(vdq->vdq_lookup, &vdq->vdq_ether->ether_dhost, ETHERADDRL);
        (void) libvarpd_overlay_resend(vdq->vdq_inst->vri_impl, vdq->vdq_otl,
            vdq->vdq_buf, vdq->vdq_bsize);

        libvarpd_plugin_query_reply(vdq->vdq_query, VARPD_LOOKUP_DROP);
        umem_free(vdq, sizeof (varpd_dhcp_query_t));
}

/*
 * Inject a gratuitous ARP packet to the specified mac address.
 */
void
libvarpd_inject_arp(varpd_provider_handle_t *vph, const uint16_t vlan,
    const uint8_t *srcmac, const struct in_addr *srcip, const uint8_t *dstmac)
{
        char buf[500];
        size_t bsize = 0;
        struct ether_arp *ea;
        varpd_instance_t *inst = (varpd_instance_t *)vph;

        if (vlan != 0) {
                struct ether_vlan_header *eh;
                eh = (struct ether_vlan_header *)(buf + bsize);
                bsize += sizeof (struct ether_vlan_header);
                bcopy(dstmac, &eh->ether_dhost, ETHERADDRL);
                bcopy(srcmac, &eh->ether_shost, ETHERADDRL);
                eh->ether_tpid = htons(ETHERTYPE_VLAN);
                eh->ether_tci = htons(VLAN_TCI(0, ETHER_CFI, vlan));
                eh->ether_type = htons(ETHERTYPE_ARP);
        } else {
                struct ether_header *eh;
                eh = (struct ether_header *)(buf + bsize);
                bsize += sizeof (struct ether_header);
                bcopy(dstmac, &eh->ether_dhost, ETHERADDRL);
                bcopy(srcmac, &eh->ether_shost, ETHERADDRL);
                eh->ether_type = htons(ETHERTYPE_ARP);
        }

        ea = (struct ether_arp *)(buf + bsize);
        bsize += sizeof (struct ether_arp);
        ea->ea_hdr.ar_hrd = htons(ARPHRD_ETHER);
        ea->ea_hdr.ar_pro = htons(ETHERTYPE_IP);
        ea->ea_hdr.ar_hln = ETHERADDRL;
        ea->ea_hdr.ar_pln = sizeof (struct in_addr);
        ea->ea_hdr.ar_op = htons(ARPOP_REQUEST);
        bcopy(srcmac, ea->arp_sha, ETHERADDRL);
        bcopy(srcip, ea->arp_spa, sizeof (struct in_addr));
        bcopy(libvarpd_arp_bcast, ea->arp_tha, ETHERADDRL);
        bcopy(srcip, ea->arp_tpa, sizeof (struct in_addr));

        (void) libvarpd_overlay_instance_inject(inst, buf, bsize);
}