#include <libvarpd_provider.h>
#include <umem.h>
#include <errno.h>
#include <thread.h>
#include <synch.h>
#include <strings.h>
#include <assert.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <libnvpair.h>
typedef struct varpd_direct {
overlay_plugin_dest_t vad_dest;
mutex_t vad_lock;
boolean_t vad_hip;
boolean_t vad_hport;
struct in6_addr vad_ip;
uint16_t vad_port;
} varpd_direct_t;
static const char *varpd_direct_props[] = {
"direct/dest_ip",
"direct/dest_port"
};
static boolean_t
varpd_direct_valid_dest(overlay_plugin_dest_t dest)
{
if (dest & ~(OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT))
return (B_FALSE);
if (!(dest & (OVERLAY_PLUGIN_D_IP | OVERLAY_PLUGIN_D_PORT)))
return (B_FALSE);
return (B_TRUE);
}
static int
varpd_direct_create(varpd_provider_handle_t *hdl, void **outp,
overlay_plugin_dest_t dest)
{
int ret;
varpd_direct_t *vdp;
if (varpd_direct_valid_dest(dest) == B_FALSE)
return (ENOTSUP);
vdp = umem_alloc(sizeof (varpd_direct_t), UMEM_DEFAULT);
if (vdp == NULL)
return (ENOMEM);
if ((ret = mutex_init(&vdp->vad_lock, USYNC_THREAD | LOCK_ERRORCHECK,
NULL)) != 0) {
umem_free(vdp, sizeof (varpd_direct_t));
return (ret);
}
vdp->vad_dest = dest;
vdp->vad_hip = B_FALSE;
vdp->vad_hport = B_FALSE;
*outp = vdp;
return (0);
}
static int
varpd_direct_start(void *arg)
{
varpd_direct_t *vdp = arg;
mutex_enter(&vdp->vad_lock);
if (vdp->vad_hip == B_FALSE ||((vdp->vad_dest & OVERLAY_PLUGIN_D_IP) &&
vdp->vad_hport == B_FALSE)) {
mutex_exit(&vdp->vad_lock);
return (EAGAIN);
}
mutex_exit(&vdp->vad_lock);
return (0);
}
static void
varpd_direct_stop(void *arg)
{
}
static void
varpd_direct_destroy(void *arg)
{
varpd_direct_t *vdp = arg;
if (mutex_destroy(&vdp->vad_lock) != 0)
abort();
umem_free(vdp, sizeof (varpd_direct_t));
}
static int
varpd_direct_default(void *arg, overlay_target_point_t *otp)
{
varpd_direct_t *vdp = arg;
mutex_enter(&vdp->vad_lock);
bcopy(&vdp->vad_ip, &otp->otp_ip, sizeof (struct in6_addr));
otp->otp_port = vdp->vad_port;
mutex_exit(&vdp->vad_lock);
return (VARPD_LOOKUP_OK);
}
static int
varpd_direct_nprops(void *arg, uint_t *nprops)
{
const varpd_direct_t *vdp = arg;
*nprops = 0;
if (vdp->vad_dest & OVERLAY_PLUGIN_D_ETHERNET)
*nprops += 1;
if (vdp->vad_dest & OVERLAY_PLUGIN_D_IP)
*nprops += 1;
if (vdp->vad_dest & OVERLAY_PLUGIN_D_PORT)
*nprops += 1;
assert(*nprops == 1 || *nprops == 2);
return (0);
}
static int
varpd_direct_propinfo(void *arg, uint_t propid, varpd_prop_handle_t *vph)
{
varpd_direct_t *vdp = arg;
assert(vdp->vad_dest & OVERLAY_PLUGIN_D_IP);
if (propid == 0) {
libvarpd_prop_set_name(vph, varpd_direct_props[0]);
libvarpd_prop_set_prot(vph, OVERLAY_PROP_PERM_RRW);
libvarpd_prop_set_type(vph, OVERLAY_PROP_T_IP);
libvarpd_prop_set_nodefault(vph);
return (0);
}
if (propid == 1 && vdp->vad_dest & OVERLAY_PLUGIN_D_PORT) {
libvarpd_prop_set_name(vph, varpd_direct_props[1]);
libvarpd_prop_set_prot(vph, OVERLAY_PROP_PERM_RRW);
libvarpd_prop_set_type(vph, OVERLAY_PROP_T_UINT);
libvarpd_prop_set_nodefault(vph);
libvarpd_prop_set_range_uint32(vph, 1, UINT16_MAX);
return (0);
}
return (EINVAL);
}
static int
varpd_direct_getprop(void *arg, const char *pname, void *buf, uint32_t *sizep)
{
varpd_direct_t *vdp = arg;
if (strcmp(pname, varpd_direct_props[0]) == 0) {
if (*sizep < sizeof (struct in6_addr))
return (EOVERFLOW);
mutex_enter(&vdp->vad_lock);
if (vdp->vad_hip == B_FALSE) {
*sizep = 0;
} else {
bcopy(&vdp->vad_ip, buf, sizeof (struct in6_addr));
*sizep = sizeof (struct in6_addr);
}
mutex_exit(&vdp->vad_lock);
return (0);
}
if (strcmp(pname, varpd_direct_props[1]) == 0) {
uint64_t val;
if (*sizep < sizeof (uint64_t))
return (EOVERFLOW);
mutex_enter(&vdp->vad_lock);
if (vdp->vad_hport == B_FALSE) {
*sizep = 0;
} else {
val = vdp->vad_port;
bcopy(&val, buf, sizeof (uint64_t));
*sizep = sizeof (uint64_t);
}
mutex_exit(&vdp->vad_lock);
return (0);
}
return (EINVAL);
}
static int
varpd_direct_setprop(void *arg, const char *pname, const void *buf,
const uint32_t size)
{
varpd_direct_t *vdp = arg;
if (strcmp(pname, varpd_direct_props[0]) == 0) {
const struct in6_addr *ipv6 = buf;
if (size < sizeof (struct in6_addr))
return (EOVERFLOW);
if (IN6_IS_ADDR_V4COMPAT(ipv6))
return (EINVAL);
if (IN6_IS_ADDR_6TO4(ipv6))
return (EINVAL);
mutex_enter(&vdp->vad_lock);
bcopy(buf, &vdp->vad_ip, sizeof (struct in6_addr));
vdp->vad_hip = B_TRUE;
mutex_exit(&vdp->vad_lock);
return (0);
}
if (strcmp(pname, varpd_direct_props[1]) == 0) {
const uint64_t *valp = buf;
if (size < sizeof (uint64_t))
return (EOVERFLOW);
if (*valp == 0 || *valp > UINT16_MAX)
return (EINVAL);
mutex_enter(&vdp->vad_lock);
vdp->vad_port = (uint16_t)*valp;
vdp->vad_hport = B_TRUE;
mutex_exit(&vdp->vad_lock);
return (0);
}
return (EINVAL);
}
static int
varpd_direct_save(void *arg, nvlist_t *nvp)
{
int ret;
varpd_direct_t *vdp = arg;
mutex_enter(&vdp->vad_lock);
if (vdp->vad_hport == B_TRUE) {
if ((ret = nvlist_add_uint16(nvp, varpd_direct_props[1],
vdp->vad_port)) != 0) {
mutex_exit(&vdp->vad_lock);
return (ret);
}
}
if (vdp->vad_hip == B_TRUE) {
char buf[INET6_ADDRSTRLEN];
if (inet_ntop(AF_INET6, &vdp->vad_ip, buf, sizeof (buf)) ==
NULL)
abort();
if ((ret = nvlist_add_string(nvp, varpd_direct_props[0],
buf)) != 0) {
mutex_exit(&vdp->vad_lock);
return (ret);
}
}
mutex_exit(&vdp->vad_lock);
return (0);
}
static int
varpd_direct_restore(nvlist_t *nvp, varpd_provider_handle_t *hdl,
overlay_plugin_dest_t dest, void **outp)
{
int ret;
char *ipstr;
varpd_direct_t *vdp;
if (varpd_direct_valid_dest(dest) == B_FALSE)
return (ENOTSUP);
vdp = umem_alloc(sizeof (varpd_direct_t), UMEM_DEFAULT);
if (vdp == NULL)
return (ENOMEM);
if ((ret = mutex_init(&vdp->vad_lock, USYNC_THREAD | LOCK_ERRORCHECK,
NULL)) != 0) {
umem_free(vdp, sizeof (varpd_direct_t));
return (ret);
}
if ((ret = nvlist_lookup_uint16(nvp, varpd_direct_props[1],
&vdp->vad_port)) != 0) {
if (ret != ENOENT) {
if (mutex_destroy(&vdp->vad_lock) != 0)
abort();
umem_free(vdp, sizeof (varpd_direct_t));
return (ret);
}
vdp->vad_hport = B_FALSE;
} else {
vdp->vad_hport = B_TRUE;
}
if ((ret = nvlist_lookup_string(nvp, varpd_direct_props[0],
&ipstr)) != 0) {
if (ret != ENOENT) {
if (mutex_destroy(&vdp->vad_lock) != 0)
abort();
umem_free(vdp, sizeof (varpd_direct_t));
return (ret);
}
vdp->vad_hip = B_FALSE;
} else {
ret = inet_pton(AF_INET6, ipstr, &vdp->vad_ip);
if (ret == -1) {
assert(errno == EAFNOSUPPORT);
abort();
}
if (ret == 0) {
if (mutex_destroy(&vdp->vad_lock) != 0)
abort();
umem_free(vdp, sizeof (varpd_direct_t));
return (EINVAL);
}
}
*outp = vdp;
return (0);
}
static const varpd_plugin_ops_t varpd_direct_ops = {
0,
varpd_direct_create,
varpd_direct_start,
varpd_direct_stop,
varpd_direct_destroy,
varpd_direct_default,
NULL,
varpd_direct_nprops,
varpd_direct_propinfo,
varpd_direct_getprop,
varpd_direct_setprop,
varpd_direct_save,
varpd_direct_restore
};
#pragma init(varpd_direct_init)
static void
varpd_direct_init(void)
{
int err;
varpd_plugin_register_t *vpr;
vpr = libvarpd_plugin_alloc(VARPD_CURRENT_VERSION, &err);
if (vpr == NULL)
return;
vpr->vpr_mode = OVERLAY_TARGET_POINT;
vpr->vpr_name = "direct";
vpr->vpr_ops = &varpd_direct_ops;
(void) libvarpd_plugin_register(vpr);
libvarpd_plugin_free(vpr);
}