#include <sys/types.h>
#include <sys/file.h>
#include <sys/errno.h>
#include <sys/open.h>
#include <sys/cred.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/kmem.h>
#include <sys/conf.h>
#include <sys/stat.h>
#include <sys/cmn_err.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/modctl.h>
#include <sys/kstat.h>
#include <sys/neti.h>
#include <sys/list.h>
#include <sys/ksynch.h>
#include <sys/sysmacros.h>
#include <sys/policy.h>
#include <sys/atomic.h>
#include <sys/model.h>
#include <sys/strsun.h>
#include <sys/netstack.h>
#include <sys/hook.h>
#include <sys/hook_event.h>
#include <sys/ipd.h>
#define IPDN_STATUS_DISABLED 0x1
#define IPDN_STATUS_ENABLED 0x2
#define IPDN_STATUS_CONDEMNED 0x4
#define IPDN_HOOK_NONE 0x0
#define IPDN_HOOK_V4IN 0x1
#define IPDN_HOOK_V4OUT 0x2
#define IPDN_HOOK_V6IN 0x4
#define IPDN_HOOK_V6OUT 0x8
#define IPDN_HOOK_ALL 0xf
typedef struct ipd_nskstat {
kstat_named_t ink_ndrops;
kstat_named_t ink_ncorrupts;
kstat_named_t ink_ndelays;
} ipd_nskstat_t;
typedef struct ipd_netstack {
list_node_t ipdn_link;
netid_t ipdn_netid;
zoneid_t ipdn_zoneid;
kstat_t *ipdn_kstat;
ipd_nskstat_t ipdn_ksdata;
kmutex_t ipdn_lock;
int ipdn_status;
net_handle_t ipdn_v4hdl;
net_handle_t ipdn_v6hdl;
int ipdn_hooked;
hook_t *ipdn_v4in;
hook_t *ipdn_v4out;
hook_t *ipdn_v6in;
hook_t *ipdn_v6out;
int ipdn_enabled;
int ipdn_corrupt;
int ipdn_drop;
uint_t ipdn_delay;
long ipdn_rand;
} ipd_netstack_t;
static dev_info_t *ipd_devi;
static net_instance_t *ipd_neti;
static unsigned int ipd_max_delay = IPD_MAX_DELAY;
static kmutex_t ipd_nsl_lock;
static list_t ipd_nsl;
static kmutex_t ipd_nactive_lock;
static unsigned int ipd_nactive;
static int ipd_nactive_fudge = 4;
static int
ipd_nextrand(ipd_netstack_t *ins)
{
ins->ipdn_rand = ins->ipdn_rand * 1103515245L + 12345;
return (ins->ipdn_rand & 0x7fffffff);
}
static void
ipd_ksbump(kstat_named_t *nkp)
{
atomic_inc_64(&nkp->value.ui64);
}
static int
ipd_hook(hook_event_token_t event, hook_data_t data, void *arg)
{
unsigned char *crp;
int dwait, corrupt, drop, rand, off, status;
mblk_t *mbp;
ipd_netstack_t *ins = arg;
hook_pkt_event_t *pkt = (hook_pkt_event_t *)data;
mutex_enter(&ins->ipdn_lock);
status = ins->ipdn_status;
dwait = ins->ipdn_delay;
corrupt = ins->ipdn_corrupt;
drop = ins->ipdn_drop;
rand = ipd_nextrand(ins);
mutex_exit(&ins->ipdn_lock);
if (status & IPDN_STATUS_CONDEMNED)
return (0);
if (drop != 0 && rand % 100 < drop) {
freemsg(*pkt->hpe_mp);
*pkt->hpe_mp = NULL;
pkt->hpe_mb = NULL;
pkt->hpe_hdr = NULL;
ipd_ksbump(&ins->ipdn_ksdata.ink_ndrops);
return (1);
}
if (dwait != 0) {
if (dwait < TICK_TO_USEC(1))
drv_usecwait(dwait);
else
delay(drv_usectohz(dwait));
ipd_ksbump(&ins->ipdn_ksdata.ink_ndelays);
}
if (corrupt != 0 && rand % 100 < corrupt) {
mbp = *pkt->hpe_mp;
while (mbp != NULL) {
if (mbp->b_wptr == mbp->b_rptr)
continue;
if (DB_TYPE(mbp) != M_DATA)
continue;
off = rand % ((uintptr_t)mbp->b_wptr -
(uintptr_t)mbp->b_rptr);
crp = mbp->b_rptr + off;
off = rand % 8;
*crp = *crp ^ (1 << off);
mbp = mbp->b_cont;
}
ipd_ksbump(&ins->ipdn_ksdata.ink_ncorrupts);
}
return (0);
}
static int
ipd_setup_hooks(ipd_netstack_t *ins)
{
ASSERT(MUTEX_HELD(&ins->ipdn_lock));
ins->ipdn_v4hdl = net_protocol_lookup(ins->ipdn_netid, NHF_INET);
if (ins->ipdn_v4hdl == NULL)
goto cleanup;
ins->ipdn_v6hdl = net_protocol_lookup(ins->ipdn_netid, NHF_INET6);
if (ins->ipdn_v6hdl == NULL)
goto cleanup;
ins->ipdn_v4in = hook_alloc(HOOK_VERSION);
if (ins->ipdn_v4in == NULL)
goto cleanup;
ins->ipdn_v4in->h_flags = 0;
ins->ipdn_v4in->h_hint = HH_NONE;
ins->ipdn_v4in->h_hintvalue = 0;
ins->ipdn_v4in->h_func = ipd_hook;
ins->ipdn_v4in->h_arg = ins;
ins->ipdn_v4in->h_name = "ipd IPv4 in";
if (net_hook_register(ins->ipdn_v4hdl, NH_PHYSICAL_IN,
ins->ipdn_v4in) != 0)
goto cleanup;
ins->ipdn_hooked |= IPDN_HOOK_V4IN;
ins->ipdn_v4out = hook_alloc(HOOK_VERSION);
if (ins->ipdn_v4out == NULL)
goto cleanup;
ins->ipdn_v4out->h_flags = 0;
ins->ipdn_v4out->h_hint = HH_NONE;
ins->ipdn_v4out->h_hintvalue = 0;
ins->ipdn_v4out->h_func = ipd_hook;
ins->ipdn_v4out->h_arg = ins;
ins->ipdn_v4out->h_name = "ipd IPv4 out";
if (net_hook_register(ins->ipdn_v4hdl, NH_PHYSICAL_OUT,
ins->ipdn_v4out) != 0)
goto cleanup;
ins->ipdn_hooked |= IPDN_HOOK_V4OUT;
ins->ipdn_v6in = hook_alloc(HOOK_VERSION);
if (ins->ipdn_v6in == NULL)
goto cleanup;
ins->ipdn_v6in->h_flags = 0;
ins->ipdn_v6in->h_hint = HH_NONE;
ins->ipdn_v6in->h_hintvalue = 0;
ins->ipdn_v6in->h_func = ipd_hook;
ins->ipdn_v6in->h_arg = ins;
ins->ipdn_v6in->h_name = "ipd IPv6 in";
if (net_hook_register(ins->ipdn_v6hdl, NH_PHYSICAL_IN,
ins->ipdn_v6in) != 0)
goto cleanup;
ins->ipdn_hooked |= IPDN_HOOK_V6IN;
ins->ipdn_v6out = hook_alloc(HOOK_VERSION);
if (ins->ipdn_v6out == NULL)
goto cleanup;
ins->ipdn_v6out->h_flags = 0;
ins->ipdn_v6out->h_hint = HH_NONE;
ins->ipdn_v6out->h_hintvalue = 0;
ins->ipdn_v6out->h_func = ipd_hook;
ins->ipdn_v6out->h_arg = ins;
ins->ipdn_v6out->h_name = "ipd IPv6 out";
if (net_hook_register(ins->ipdn_v6hdl, NH_PHYSICAL_OUT,
ins->ipdn_v6out) != 0)
goto cleanup;
ins->ipdn_hooked |= IPDN_HOOK_V6OUT;
mutex_enter(&ipd_nactive_lock);
ipd_nactive++;
mutex_exit(&ipd_nactive_lock);
return (0);
cleanup:
if (ins->ipdn_hooked & IPDN_HOOK_V6OUT)
(void) net_hook_unregister(ins->ipdn_v6hdl, NH_PHYSICAL_OUT,
ins->ipdn_v6out);
if (ins->ipdn_hooked & IPDN_HOOK_V6IN)
(void) net_hook_unregister(ins->ipdn_v6hdl, NH_PHYSICAL_IN,
ins->ipdn_v6in);
if (ins->ipdn_hooked & IPDN_HOOK_V4OUT)
(void) net_hook_unregister(ins->ipdn_v4hdl, NH_PHYSICAL_OUT,
ins->ipdn_v4out);
if (ins->ipdn_hooked & IPDN_HOOK_V4IN)
(void) net_hook_unregister(ins->ipdn_v4hdl, NH_PHYSICAL_IN,
ins->ipdn_v4in);
ins->ipdn_hooked = IPDN_HOOK_NONE;
if (ins->ipdn_v6out != NULL)
hook_free(ins->ipdn_v6out);
if (ins->ipdn_v6in != NULL)
hook_free(ins->ipdn_v6in);
if (ins->ipdn_v4out != NULL)
hook_free(ins->ipdn_v4out);
if (ins->ipdn_v4in != NULL)
hook_free(ins->ipdn_v4in);
if (ins->ipdn_v6hdl != NULL)
(void) net_protocol_release(ins->ipdn_v6hdl);
if (ins->ipdn_v4hdl != NULL)
(void) net_protocol_release(ins->ipdn_v4hdl);
return (1);
}
static void
ipd_teardown_hooks(ipd_netstack_t *ins)
{
ASSERT(ins->ipdn_hooked == IPDN_HOOK_ALL);
VERIFY(net_hook_unregister(ins->ipdn_v6hdl, NH_PHYSICAL_OUT,
ins->ipdn_v6out) == 0);
VERIFY(net_hook_unregister(ins->ipdn_v6hdl, NH_PHYSICAL_IN,
ins->ipdn_v6in) == 0);
VERIFY(net_hook_unregister(ins->ipdn_v4hdl, NH_PHYSICAL_OUT,
ins->ipdn_v4out) == 0);
VERIFY(net_hook_unregister(ins->ipdn_v4hdl, NH_PHYSICAL_IN,
ins->ipdn_v4in) == 0);
ins->ipdn_hooked = IPDN_HOOK_NONE;
hook_free(ins->ipdn_v6out);
hook_free(ins->ipdn_v6in);
hook_free(ins->ipdn_v4out);
hook_free(ins->ipdn_v4in);
VERIFY(net_protocol_release(ins->ipdn_v6hdl) == 0);
VERIFY(net_protocol_release(ins->ipdn_v4hdl) == 0);
mutex_enter(&ipd_nactive_lock);
ipd_nactive--;
mutex_exit(&ipd_nactive_lock);
}
static int
ipd_check_hooks(ipd_netstack_t *ins, int type, boolean_t enable)
{
int olden, rval;
olden = ins->ipdn_enabled;
if (enable)
ins->ipdn_enabled |= type;
else
ins->ipdn_enabled &= ~type;
if (olden == 0 && ins->ipdn_enabled != 0) {
rval = ipd_setup_hooks(ins);
if (rval != 0) {
ins->ipdn_enabled &= ~type;
ASSERT(ins->ipdn_enabled == 0);
return (rval);
}
return (0);
}
if (olden != 0 && ins->ipdn_enabled == 0) {
ASSERT(olden != 0);
mutex_exit(&ins->ipdn_lock);
ipd_teardown_hooks(ins);
mutex_enter(&ins->ipdn_lock);
return (0);
}
ASSERT((olden == 0) == (ins->ipdn_enabled == 0));
return (0);
}
static int
ipd_toggle_corrupt(ipd_netstack_t *ins, int percent)
{
int rval;
ASSERT(MUTEX_HELD(&ins->ipdn_lock));
if (percent < 0 || percent > 100)
return (ERANGE);
if (percent == ins->ipdn_corrupt)
return (0);
ins->ipdn_corrupt = percent;
rval = ipd_check_hooks(ins, IPD_CORRUPT, percent != 0);
if (rval != 0)
ins->ipdn_corrupt = 0;
return (rval);
}
static int
ipd_toggle_delay(ipd_netstack_t *ins, uint32_t delay)
{
int rval;
ASSERT(MUTEX_HELD(&ins->ipdn_lock));
if (delay > ipd_max_delay)
return (ERANGE);
if (delay == ins->ipdn_delay)
return (0);
ins->ipdn_delay = delay;
rval = ipd_check_hooks(ins, IPD_DELAY, delay != 0);
if (rval != 0)
ins->ipdn_delay = 0;
return (rval);
}
static int
ipd_toggle_drop(ipd_netstack_t *ins, int percent)
{
int rval;
ASSERT(MUTEX_HELD(&ins->ipdn_lock));
if (percent < 0 || percent > 100)
return (ERANGE);
if (percent == ins->ipdn_drop)
return (0);
ins->ipdn_drop = percent;
rval = ipd_check_hooks(ins, IPD_DROP, percent != 0);
if (rval != 0)
ins->ipdn_drop = 0;
return (rval);
}
static int
ipd_ioctl_perturb(ipd_ioc_perturb_t *ipi, cred_t *cr, intptr_t cmd)
{
zoneid_t zid;
ipd_netstack_t *ins;
int rval = 0;
zid = crgetzoneid(cr);
if (zid != GLOBAL_ZONEID)
ipi->ipip_zoneid = zid;
if (zoneid_to_netstackid(ipi->ipip_zoneid) == GLOBAL_NETSTACKID &&
zid != GLOBAL_ZONEID)
return (EPERM);
mutex_enter(&ipd_nsl_lock);
for (ins = list_head(&ipd_nsl); ins != NULL;
ins = list_next(&ipd_nsl, ins)) {
if (ins->ipdn_zoneid == ipi->ipip_zoneid)
break;
}
if (ins == NULL) {
mutex_exit(&ipd_nsl_lock);
return (EINVAL);
}
mutex_enter(&ins->ipdn_lock);
if (ins->ipdn_status & IPDN_STATUS_CONDEMNED) {
rval = ESHUTDOWN;
goto cleanup;
}
switch (cmd) {
case IPDIOC_CORRUPT:
rval = ipd_toggle_corrupt(ins, ipi->ipip_arg);
break;
case IPDIOC_DELAY:
rval = ipd_toggle_delay(ins, ipi->ipip_arg);
break;
case IPDIOC_DROP:
rval = ipd_toggle_drop(ins, ipi->ipip_arg);
break;
}
cleanup:
mutex_exit(&ins->ipdn_lock);
mutex_exit(&ipd_nsl_lock);
return (rval);
}
static int
ipd_ioctl_remove(ipd_ioc_perturb_t *ipi, cred_t *cr)
{
zoneid_t zid;
ipd_netstack_t *ins;
int rval = 0;
zid = crgetzoneid(cr);
if (zid != GLOBAL_ZONEID)
ipi->ipip_zoneid = zid;
if (zoneid_to_netstackid(ipi->ipip_zoneid) == GLOBAL_NETSTACKID &&
zid != GLOBAL_ZONEID)
return (EPERM);
mutex_enter(&ipd_nsl_lock);
for (ins = list_head(&ipd_nsl); ins != NULL;
ins = list_next(&ipd_nsl, ins)) {
if (ins->ipdn_zoneid == ipi->ipip_zoneid)
break;
}
if (ins == NULL) {
mutex_exit(&ipd_nsl_lock);
return (EINVAL);
}
mutex_enter(&ins->ipdn_lock);
if (ins->ipdn_status & IPDN_STATUS_CONDEMNED) {
rval = 0;
goto cleanup;
}
rval = EINVAL;
if (ipi->ipip_arg & IPD_CORRUPT) {
ins->ipdn_corrupt = 0;
(void) ipd_check_hooks(ins, IPD_CORRUPT, B_FALSE);
rval = 0;
}
if (ipi->ipip_arg & IPD_DELAY) {
ins->ipdn_delay = 0;
(void) ipd_check_hooks(ins, IPD_DELAY, B_FALSE);
rval = 0;
}
if (ipi->ipip_arg & IPD_DROP) {
ins->ipdn_drop = 0;
(void) ipd_check_hooks(ins, IPD_DROP, B_FALSE);
rval = 0;
}
cleanup:
mutex_exit(&ins->ipdn_lock);
mutex_exit(&ipd_nsl_lock);
return (rval);
}
static int
ipd_ioctl_list(intptr_t arg, cred_t *cr)
{
zoneid_t zid;
ipd_ioc_info_t *configs;
ipd_netstack_t *ins;
uint_t azones, rzones, nzones, cur;
int rval = 0;
STRUCT_DECL(ipd_ioc_list, h);
STRUCT_INIT(h, get_udatamodel());
if (ddi_copyin((void *)arg, STRUCT_BUF(h),
STRUCT_SIZE(h), 0) != 0)
return (EFAULT);
zid = crgetzoneid(cr);
rzones = STRUCT_FGET(h, ipil_nzones);
if (rzones == 0) {
if (zid == GLOBAL_ZONEID) {
mutex_enter(&ipd_nactive_lock);
rzones = ipd_nactive + ipd_nactive_fudge;
mutex_exit(&ipd_nactive_lock);
} else {
rzones = 1;
}
STRUCT_FSET(h, ipil_nzones, rzones);
if (ddi_copyout(STRUCT_BUF(h), (void *)arg,
STRUCT_SIZE(h), 0) != 0)
return (EFAULT);
return (0);
}
mutex_enter(&ipd_nsl_lock);
if (zid == GLOBAL_ZONEID) {
azones = ipd_nactive;
} else {
azones = 1;
}
configs = kmem_alloc(sizeof (ipd_ioc_info_t) * azones, KM_SLEEP);
cur = 0;
for (ins = list_head(&ipd_nsl); ins != NULL;
ins = list_next(&ipd_nsl, ins)) {
if (ins->ipdn_enabled == 0)
continue;
ASSERT(cur < azones);
if (zid == GLOBAL_ZONEID || zid == ins->ipdn_zoneid) {
configs[cur].ipii_zoneid = ins->ipdn_zoneid;
mutex_enter(&ins->ipdn_lock);
configs[cur].ipii_corrupt = ins->ipdn_corrupt;
configs[cur].ipii_delay = ins->ipdn_delay;
configs[cur].ipii_drop = ins->ipdn_drop;
mutex_exit(&ins->ipdn_lock);
++cur;
}
if (zid != GLOBAL_ZONEID && zid == ins->ipdn_zoneid)
break;
}
mutex_exit(&ipd_nsl_lock);
ASSERT(zid != GLOBAL_ZONEID || cur == azones);
if (cur == 0)
STRUCT_FSET(h, ipil_nzones, 0);
else
STRUCT_FSET(h, ipil_nzones, cur);
nzones = MIN(cur, rzones);
if (nzones > 0) {
if (ddi_copyout(configs, STRUCT_FGETP(h, ipil_info),
nzones * sizeof (ipd_ioc_info_t), 0) != 0)
rval = EFAULT;
}
kmem_free(configs, sizeof (ipd_ioc_info_t) * azones);
if (ddi_copyout(STRUCT_BUF(h), (void *)arg, STRUCT_SIZE(h), 0) != 0)
return (EFAULT);
return (rval);
}
static void *
ipd_nin_create(const netid_t id)
{
ipd_netstack_t *ins;
ipd_nskstat_t *ink;
ins = kmem_zalloc(sizeof (ipd_netstack_t), KM_SLEEP);
ins->ipdn_status = IPDN_STATUS_DISABLED;
ins->ipdn_netid = id;
ins->ipdn_zoneid = netstackid_to_zoneid(id);
ins->ipdn_rand = gethrtime();
mutex_init(&ins->ipdn_lock, NULL, MUTEX_DRIVER, NULL);
ins->ipdn_kstat = net_kstat_create(id, "ipd", ins->ipdn_zoneid,
"ipd", "net", KSTAT_TYPE_NAMED,
sizeof (ipd_nskstat_t) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL);
if (ins->ipdn_kstat != NULL) {
if (ins->ipdn_zoneid != GLOBAL_ZONEID)
kstat_zone_add(ins->ipdn_kstat, GLOBAL_ZONEID);
ink = &ins->ipdn_ksdata;
ins->ipdn_kstat->ks_data = ink;
kstat_named_init(&ink->ink_ncorrupts, "corrupts",
KSTAT_DATA_UINT64);
kstat_named_init(&ink->ink_ndrops, "drops", KSTAT_DATA_UINT64);
kstat_named_init(&ink->ink_ndelays, "delays",
KSTAT_DATA_UINT64);
kstat_install(ins->ipdn_kstat);
}
mutex_enter(&ipd_nsl_lock);
list_insert_tail(&ipd_nsl, ins);
mutex_exit(&ipd_nsl_lock);
return (ins);
}
static void
ipd_nin_shutdown(const netid_t id, void *arg)
{
ipd_netstack_t *ins = arg;
VERIFY(id == ins->ipdn_netid);
mutex_enter(&ins->ipdn_lock);
ASSERT(ins->ipdn_status == IPDN_STATUS_DISABLED ||
ins->ipdn_status == IPDN_STATUS_ENABLED);
ins->ipdn_status |= IPDN_STATUS_CONDEMNED;
if (ins->ipdn_kstat != NULL)
net_kstat_delete(id, ins->ipdn_kstat);
mutex_exit(&ins->ipdn_lock);
}
static void
ipd_nin_destroy(const netid_t id, void *arg)
{
ipd_netstack_t *ins = arg;
mutex_enter(&ipd_nsl_lock);
list_remove(&ipd_nsl, ins);
mutex_exit(&ipd_nsl_lock);
if (ins->ipdn_hooked)
ipd_teardown_hooks(ins);
mutex_destroy(&ins->ipdn_lock);
kmem_free(ins, sizeof (ipd_netstack_t));
}
static int
ipd_open(dev_t *devp, int flag, int otype, cred_t *credp)
{
if (flag & FEXCL || flag & FNDELAY)
return (EINVAL);
if (otype != OTYP_CHR)
return (EINVAL);
if (!(flag & FREAD && flag & FWRITE))
return (EINVAL);
if (secpolicy_ip_config(credp, B_FALSE) != 0)
return (EPERM);
return (0);
}
static int
ipd_ioctl(dev_t dev, int cmd, intptr_t arg, int md, cred_t *cr, int *rv)
{
int rval;
ipd_ioc_perturb_t ipip;
switch (cmd) {
case IPDIOC_CORRUPT:
case IPDIOC_DELAY:
case IPDIOC_DROP:
if (ddi_copyin((void *)arg, &ipip, sizeof (ipd_ioc_perturb_t),
0) != 0)
return (EFAULT);
rval = ipd_ioctl_perturb(&ipip, cr, cmd);
return (rval);
case IPDIOC_REMOVE:
if (ddi_copyin((void *)arg, &ipip, sizeof (ipd_ioc_perturb_t),
0) != 0)
return (EFAULT);
rval = ipd_ioctl_remove(&ipip, cr);
return (rval);
case IPDIOC_LIST:
return (ipd_ioctl_list(arg, cr));
default:
break;
}
return (ENOTTY);
}
static int
ipd_close(dev_t dev, int flag, int otype, cred_t *credp)
{
return (0);
}
static int
ipd_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
minor_t instance;
if (cmd != DDI_ATTACH)
return (DDI_FAILURE);
if (ipd_devi != NULL)
return (DDI_FAILURE);
instance = ddi_get_instance(dip);
if (ddi_create_minor_node(dip, "ipd", S_IFCHR, instance,
DDI_PSEUDO, 0) == DDI_FAILURE)
return (DDI_FAILURE);
ipd_neti = net_instance_alloc(NETINFO_VERSION);
if (ipd_neti == NULL) {
ddi_remove_minor_node(dip, NULL);
return (DDI_FAILURE);
}
list_create(&ipd_nsl, sizeof (ipd_netstack_t),
offsetof(ipd_netstack_t, ipdn_link));
mutex_init(&ipd_nsl_lock, NULL, MUTEX_DRIVER, NULL);
mutex_init(&ipd_nactive_lock, NULL, MUTEX_DRIVER, NULL);
ipd_neti->nin_name = "ipd";
ipd_neti->nin_create = ipd_nin_create;
ipd_neti->nin_destroy = ipd_nin_destroy;
ipd_neti->nin_shutdown = ipd_nin_shutdown;
if (net_instance_register(ipd_neti) == DDI_FAILURE) {
net_instance_free(ipd_neti);
ddi_remove_minor_node(dip, NULL);
}
ddi_report_dev(dip);
ipd_devi = dip;
return (DDI_SUCCESS);
}
static int
ipd_getinfo(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
int error;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
*result = ipd_devi;
error = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)getminor((dev_t)arg);
error = DDI_SUCCESS;
break;
default:
error = DDI_FAILURE;
break;
}
return (error);
}
static int
ipd_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd != DDI_DETACH)
return (DDI_FAILURE);
mutex_enter(&ipd_nactive_lock);
if (ipd_nactive > 0) {
mutex_exit(&ipd_nactive_lock);
return (EBUSY);
}
mutex_exit(&ipd_nactive_lock);
ASSERT(dip == ipd_devi);
ddi_remove_minor_node(dip, NULL);
ipd_devi = NULL;
if (ipd_neti != NULL) {
VERIFY(net_instance_unregister(ipd_neti) == 0);
net_instance_free(ipd_neti);
}
mutex_destroy(&ipd_nsl_lock);
mutex_destroy(&ipd_nactive_lock);
list_destroy(&ipd_nsl);
return (DDI_SUCCESS);
}
static struct cb_ops ipd_cb_ops = {
ipd_open,
ipd_close,
nodev,
nodev,
nodev,
nodev,
nodev,
ipd_ioctl,
nodev,
nodev,
nodev,
nochpoll,
ddi_prop_op,
NULL,
D_NEW | D_MP,
CB_REV,
nodev,
nodev
};
static struct dev_ops ipd_ops = {
DEVO_REV,
0,
ipd_getinfo,
nulldev,
nulldev,
ipd_attach,
ipd_detach,
nodev,
&ipd_cb_ops,
NULL,
nodev,
ddi_quiesce_not_needed
};
static struct modldrv modldrv = {
&mod_driverops,
"Internet packet disturber",
&ipd_ops
};
static struct modlinkage modlinkage = {
MODREV_1,
{ (void *)&modldrv, NULL }
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}