#include <sys/param.h>
#include <sys/systm.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/queue.h>
#include <sys/filio.h>
#include <sys/policy.h>
#include <sys/cmn_err.h>
#include <sys/uio.h>
#include <sys/file.h>
#include <sys/sysmacros.h>
#include <sys/zone.h>
#include <sys/socket.h>
#include <sys/errno.h>
#include <sys/poll.h>
#include <sys/dlpi.h>
#include <sys/neti.h>
#include <net/if.h>
#include <net/bpf.h>
#include <net/bpfdesc.h>
#include <net/dlt.h>
#include <netinet/in.h>
#include <sys/mac.h>
#include <sys/mac_client.h>
#include <sys/mac_impl.h>
#include <sys/time_std_impl.h>
#include <sys/hook.h>
#include <sys/hook_event.h>
#define mtod(_v, _t) (_t)((_v)->b_rptr)
#define M_LEN(_m) ((_m)->b_wptr - (_m)->b_rptr)
#define BPF_BUFSIZE (32 * 1024)
typedef void *(*cp_fn_t)(void *, const void *, size_t);
int bpf_bufsize = BPF_BUFSIZE;
int bpf_maxbufsize = (16 * 1024 * 1024);
static mod_hash_t *bpf_hash = NULL;
static kcondvar_t bpf_dlt_waiter;
static kmutex_t bpf_mtx;
static bpf_kstats_t ks_stats;
static bpf_kstats_t bpf_kstats = {
{ "readWait", KSTAT_DATA_UINT64 },
{ "writeOk", KSTAT_DATA_UINT64 },
{ "writeError", KSTAT_DATA_UINT64 },
{ "receive", KSTAT_DATA_UINT64 },
{ "captured", KSTAT_DATA_UINT64 },
{ "dropped", KSTAT_DATA_UINT64 },
};
static kstat_t *bpf_ksp;
LIST_HEAD(, bpf_d) bpf_list;
static int bpf_allocbufs(struct bpf_d *);
static void bpf_clear_timeout(struct bpf_d *);
static void bpf_deliver(struct bpf_d *, cp_fn_t,
void *, uint_t, uint_t, boolean_t);
static void bpf_freed(struct bpf_d *);
static int bpf_ifname(struct bpf_d *d, char *, int);
static void *bpf_mcpy(void *, const void *, size_t);
static int bpf_attachd(struct bpf_d *, const char *, int);
static void bpf_detachd(struct bpf_d *);
static int bpf_setif(struct bpf_d *, char *, int);
static void bpf_timed_out(void *);
static inline void
bpf_wakeup(struct bpf_d *);
static void catchpacket(struct bpf_d *, uchar_t *, uint_t, uint_t,
cp_fn_t, struct timeval *);
static void reset_d(struct bpf_d *);
static int bpf_getdltlist(struct bpf_d *, struct bpf_dltlist *);
static int bpf_setdlt(struct bpf_d *, void *);
static void bpf_dev_add(struct bpf_d *);
static struct bpf_d *bpf_dev_find(minor_t);
static struct bpf_d *bpf_dev_get(minor_t);
static void bpf_dev_remove(struct bpf_d *);
static int
bpf_movein(struct uio *uio, int linktype, int mtu, mblk_t **mp)
{
mblk_t *m;
int error;
int len;
int hlen;
int align;
switch (linktype) {
case DLT_EN10MB:
hlen = sizeof (struct ether_header);
break;
case DLT_FDDI:
hlen = 16;
break;
case DLT_NULL:
hlen = 0;
break;
case DLT_IPOIB:
hlen = 44;
break;
default:
return (EIO);
}
align = 4 - (hlen & 3);
len = uio->uio_resid;
if (len < hlen || len - hlen > mtu)
return (EMSGSIZE);
m = allocb(len + align, BPRI_MED);
if (m == NULL) {
error = ENOBUFS;
goto bad;
}
if (align > 0)
m->b_rptr += align;
m->b_wptr = m->b_rptr + len;
error = uiomove(mtod(m, void *), len, UIO_WRITE, uio);
if (error)
goto bad;
*mp = m;
return (0);
bad:
if (m != NULL)
freemsg(m);
return (error);
}
static int
bpf_attachd(struct bpf_d *d, const char *ifname, int dlt)
{
bpf_provider_list_t *bp;
bpf_provider_t *bpr;
boolean_t zonematch;
zoneid_t niczone;
uintptr_t mcip;
zoneid_t zone;
uint_t nicdlt;
uintptr_t mh;
int hdrlen;
int error;
ASSERT(d->bd_bif == (uintptr_t)NULL);
ASSERT(d->bd_mcip == (uintptr_t)NULL);
zone = d->bd_zone;
zonematch = B_TRUE;
error = 0;
bpr = NULL;
again:
mh = 0;
mcip = 0;
LIST_FOREACH(bp, &bpf_providers, bpl_next) {
bpr = bp->bpl_what;
error = MBPF_OPEN(bpr, ifname, &mh, zone);
if (error != 0)
goto next;
error = MBPF_CLIENT_OPEN(bpr, mh, &mcip);
if (error != 0)
goto next;
error = MBPF_GET_DLT(bpr, mh, &nicdlt);
if (error != 0)
goto next;
nicdlt = bpf_dl_to_dlt(nicdlt);
if (dlt != -1 && dlt != nicdlt) {
error = ENOENT;
goto next;
}
error = MBPF_GET_ZONE(bpr, mh, &niczone);
if (error != 0)
goto next;
DTRACE_PROBE4(bpf__attach, struct bpf_provider_s *, bpr,
uintptr_t, mh, int, nicdlt, zoneid_t, niczone);
if (zonematch && niczone != zone) {
error = ENOENT;
goto next;
}
break;
next:
if (mcip != 0) {
MBPF_CLIENT_CLOSE(bpr, mcip);
mcip = 0;
}
if (mh != 0) {
MBPF_CLOSE(bpr, mh);
mh = 0;
}
}
if (error != 0) {
if (zonematch && (zone == GLOBAL_ZONEID)) {
zonematch = B_FALSE;
goto again;
}
return (error);
}
if (bpr == NULL)
return (ENOENT);
d->bd_mac = *bpr;
d->bd_mcip = mcip;
d->bd_bif = mh;
d->bd_dlt = nicdlt;
hdrlen = bpf_dl_hdrsize(nicdlt);
d->bd_hdrlen = BPF_WORDALIGN(hdrlen + SIZEOF_BPF_HDR) - hdrlen;
(void) strlcpy(d->bd_ifname, MBPF_CLIENT_NAME(&d->bd_mac, mcip),
sizeof (d->bd_ifname));
(void) MBPF_GET_LINKID(&d->bd_mac, d->bd_ifname, &d->bd_linkid,
zone);
(void) MBPF_PROMISC_ADD(&d->bd_mac, d->bd_mcip, 0, d,
&d->bd_promisc_handle, d->bd_promisc_flags);
return (0);
}
static void
bpf_detachd(struct bpf_d *d)
{
uintptr_t mph;
uintptr_t mch;
uintptr_t mh;
ASSERT(d->bd_inuse == -1);
mch = d->bd_mcip;
d->bd_mcip = 0;
mh = d->bd_bif;
d->bd_bif = 0;
if (d->bd_promisc)
d->bd_promisc = 0;
mph = d->bd_promisc_handle;
d->bd_promisc_handle = 0;
mutex_exit(&d->bd_lock);
if (mph != 0)
MBPF_PROMISC_REMOVE(&d->bd_mac, mph);
if (mch != 0)
MBPF_CLIENT_CLOSE(&d->bd_mac, mch);
if (mh != 0)
MBPF_CLOSE(&d->bd_mac, mh);
mutex_enter(&d->bd_lock);
*d->bd_ifname = '\0';
(void) memset(&d->bd_mac, 0, sizeof (d->bd_mac));
}
int
bpfilterattach(void)
{
bpf_hash = mod_hash_create_idhash("bpf_dev_tab", 31,
mod_hash_null_keydtor);
if (bpf_hash == NULL)
return (ENOMEM);
(void) memcpy(&ks_stats, &bpf_kstats, sizeof (bpf_kstats));
bpf_ksp = kstat_create("bpf", 0, "global", "misc",
KSTAT_TYPE_NAMED, sizeof (bpf_kstats) / sizeof (kstat_named_t),
KSTAT_FLAG_VIRTUAL);
if (bpf_ksp != NULL) {
bpf_ksp->ks_data = &ks_stats;
kstat_install(bpf_ksp);
} else {
mod_hash_destroy_idhash(bpf_hash);
bpf_hash = NULL;
return (EEXIST);
}
cv_init(&bpf_dlt_waiter, NULL, CV_DRIVER, NULL);
mutex_init(&bpf_mtx, NULL, MUTEX_DRIVER, NULL);
LIST_INIT(&bpf_list);
return (0);
}
int
bpfilterdetach(void)
{
if (bpf_ksp != NULL) {
kstat_delete(bpf_ksp);
bpf_ksp = NULL;
}
mod_hash_destroy_idhash(bpf_hash);
bpf_hash = NULL;
cv_destroy(&bpf_dlt_waiter);
mutex_destroy(&bpf_mtx);
return (0);
}
int
bpfopen(dev_t *devp, int flag, int mode, cred_t *cred)
{
struct bpf_d *d;
uint_t dmin;
if ((flag & FWRITE) != 0) {
if (secpolicy_net_rawaccess(cred) != 0)
return (EACCES);
}
if ((flag & FREAD) != 0) {
if ((secpolicy_net_observability(cred) != 0) &&
(secpolicy_net_rawaccess(cred) != 0))
return (EACCES);
}
if ((flag & (FWRITE|FREAD)) == 0)
return (ENXIO);
d = (struct bpf_d *)kmem_zalloc(sizeof (*d), KM_SLEEP);
d->bd_bufsize = bpf_bufsize;
d->bd_fmode = flag;
d->bd_zone = crgetzoneid(cred);
d->bd_seesent = 1;
d->bd_promisc_flags = MAC_PROMISC_FLAGS_NO_PHYS|
MAC_PROMISC_FLAGS_NO_COPY;
mutex_init(&d->bd_lock, NULL, MUTEX_DRIVER, NULL);
cv_init(&d->bd_wait, NULL, CV_DRIVER, NULL);
mutex_enter(&bpf_mtx);
for (dmin = 0; dmin < L_MAXMIN; dmin++)
if (bpf_dev_find(dmin) == NULL)
break;
if (dmin == L_MAXMIN) {
mutex_exit(&bpf_mtx);
kmem_free(d, sizeof (*d));
return (ENXIO);
}
d->bd_dev = dmin;
LIST_INSERT_HEAD(&bpf_list, d, bd_list);
bpf_dev_add(d);
mutex_exit(&bpf_mtx);
*devp = makedevice(getmajor(*devp), dmin);
return (0);
}
int
bpfclose(dev_t dev, int flag, int otyp, cred_t *cred_p)
{
struct bpf_d *d = bpf_dev_get(getminor(dev));
mutex_enter(&d->bd_lock);
while (d->bd_inuse != 0) {
d->bd_waiting++;
if (cv_wait_sig(&d->bd_wait, &d->bd_lock) <= 0) {
d->bd_waiting--;
mutex_exit(&d->bd_lock);
return (EINTR);
}
d->bd_waiting--;
}
d->bd_inuse = -1;
if (d->bd_state == BPF_WAITING)
bpf_clear_timeout(d);
d->bd_state = BPF_IDLE;
if (d->bd_bif)
bpf_detachd(d);
mutex_exit(&d->bd_lock);
mutex_enter(&bpf_mtx);
LIST_REMOVE(d, bd_list);
bpf_dev_remove(d);
mutex_exit(&bpf_mtx);
mutex_enter(&d->bd_lock);
mutex_destroy(&d->bd_lock);
cv_destroy(&d->bd_wait);
bpf_freed(d);
kmem_free(d, sizeof (*d));
return (0);
}
#define ROTATE_BUFFERS(d) \
(d)->bd_hbuf = (d)->bd_sbuf; \
(d)->bd_hlen = (d)->bd_slen; \
(d)->bd_sbuf = (d)->bd_fbuf; \
(d)->bd_slen = 0; \
(d)->bd_fbuf = 0;
int
bpfread(dev_t dev, struct uio *uio, cred_t *cred)
{
struct bpf_d *d = bpf_dev_get(getminor(dev));
int timed_out;
ulong_t delay;
int error;
if ((d->bd_fmode & FREAD) == 0)
return (EBADF);
if (uio->uio_resid != d->bd_bufsize)
return (EINVAL);
mutex_enter(&d->bd_lock);
if (d->bd_state == BPF_WAITING)
bpf_clear_timeout(d);
timed_out = (d->bd_state == BPF_TIMED_OUT);
d->bd_state = BPF_IDLE;
while (d->bd_hbuf == 0) {
if (d->bd_nonblock) {
if (d->bd_slen == 0) {
mutex_exit(&d->bd_lock);
return (EWOULDBLOCK);
}
ROTATE_BUFFERS(d);
break;
}
if ((d->bd_immediate || timed_out) && d->bd_slen != 0) {
ROTATE_BUFFERS(d);
break;
}
ks_stats.kp_read_wait.value.ui64++;
delay = ddi_get_lbolt() + d->bd_rtout;
error = cv_timedwait_sig(&d->bd_wait, &d->bd_lock, delay);
if (error == 0) {
mutex_exit(&d->bd_lock);
return (EINTR);
}
if (error == -1) {
if (d->bd_hbuf)
break;
if (d->bd_slen == 0) {
mutex_exit(&d->bd_lock);
return (0);
}
ROTATE_BUFFERS(d);
}
}
mutex_exit(&d->bd_lock);
error = uiomove(d->bd_hbuf, d->bd_hlen, UIO_READ, uio);
mutex_enter(&d->bd_lock);
d->bd_fbuf = d->bd_hbuf;
d->bd_hbuf = 0;
d->bd_hlen = 0;
mutex_exit(&d->bd_lock);
return (error);
}
static inline void
bpf_wakeup(struct bpf_d *d)
{
cv_signal(&d->bd_wait);
}
static void
bpf_timed_out(void *arg)
{
struct bpf_d *d = arg;
mutex_enter(&d->bd_lock);
if (d->bd_state == BPF_WAITING) {
d->bd_state = BPF_TIMED_OUT;
if (d->bd_slen != 0)
cv_signal(&d->bd_wait);
}
mutex_exit(&d->bd_lock);
}
int
bpfwrite(dev_t dev, struct uio *uio, cred_t *cred)
{
struct bpf_d *d = bpf_dev_get(getminor(dev));
uintptr_t mch;
uint_t mtu;
mblk_t *m;
int error;
int dlt;
if ((d->bd_fmode & FWRITE) == 0)
return (EBADF);
mutex_enter(&d->bd_lock);
if (d->bd_bif == 0 || d->bd_mcip == 0 || d->bd_bif == 0) {
mutex_exit(&d->bd_lock);
return (EINTR);
}
if (uio->uio_resid == 0) {
mutex_exit(&d->bd_lock);
return (0);
}
while (d->bd_inuse < 0) {
d->bd_waiting++;
if (cv_wait_sig(&d->bd_wait, &d->bd_lock) <= 0) {
d->bd_waiting--;
mutex_exit(&d->bd_lock);
return (EINTR);
}
d->bd_waiting--;
}
mutex_exit(&d->bd_lock);
dlt = d->bd_dlt;
mch = d->bd_mcip;
MBPF_SDU_GET(&d->bd_mac, d->bd_bif, &mtu);
d->bd_inuse++;
m = NULL;
if (dlt == DLT_IPNET) {
error = EIO;
goto done;
}
error = bpf_movein(uio, dlt, mtu, &m);
if (error)
goto done;
DTRACE_PROBE4(bpf__tx, struct bpf_d *, d, int, dlt,
uint_t, mtu, mblk_t *, m);
if (M_LEN(m) > mtu) {
error = EMSGSIZE;
goto done;
}
error = MBPF_TX(&d->bd_mac, mch, m);
m = NULL;
done:
if (error == 0)
ks_stats.kp_write_ok.value.ui64++;
else
ks_stats.kp_write_error.value.ui64++;
if (m != NULL)
freemsg(m);
mutex_enter(&d->bd_lock);
d->bd_inuse--;
if ((d->bd_inuse == 0) && (d->bd_waiting != 0))
cv_signal(&d->bd_wait);
mutex_exit(&d->bd_lock);
return (error);
}
static void
reset_d(struct bpf_d *d)
{
if (d->bd_hbuf) {
d->bd_fbuf = d->bd_hbuf;
d->bd_hbuf = 0;
}
d->bd_slen = 0;
d->bd_hlen = 0;
d->bd_rcount = 0;
d->bd_dcount = 0;
d->bd_ccount = 0;
}
int
bpfioctl(dev_t dev, int cmd, intptr_t addr, int mode, cred_t *cred, int *rval)
{
struct bpf_d *d = bpf_dev_get(getminor(dev));
struct bpf_program prog;
struct lifreq lifreq;
struct ifreq ifreq;
int error = 0;
uint_t size;
mutex_enter(&d->bd_lock);
if (d->bd_state == BPF_WAITING)
bpf_clear_timeout(d);
d->bd_state = BPF_IDLE;
mutex_exit(&d->bd_lock);
switch (cmd) {
default:
error = EINVAL;
break;
case FIONREAD:
{
int n;
mutex_enter(&d->bd_lock);
n = d->bd_slen;
if (d->bd_hbuf)
n += d->bd_hlen;
mutex_exit(&d->bd_lock);
*(int *)addr = n;
break;
}
case BIOCGBLEN:
error = copyout(&d->bd_bufsize, (void *)addr,
sizeof (d->bd_bufsize));
break;
case BIOCSBLEN:
if (copyin((void *)addr, &size, sizeof (size)) != 0) {
error = EFAULT;
break;
}
mutex_enter(&d->bd_lock);
if (d->bd_bif != 0) {
error = EINVAL;
} else {
if (size > bpf_maxbufsize)
size = bpf_maxbufsize;
else if (size < BPF_MINBUFSIZE)
size = BPF_MINBUFSIZE;
d->bd_bufsize = size;
}
mutex_exit(&d->bd_lock);
if (error == 0)
error = copyout(&size, (void *)addr, sizeof (size));
break;
case BIOCSETF:
if (ddi_copyin((void *)addr, &prog, sizeof (prog), mode)) {
error = EFAULT;
break;
}
error = bpf_setf(d, &prog);
break;
case BIOCFLUSH:
mutex_enter(&d->bd_lock);
reset_d(d);
mutex_exit(&d->bd_lock);
break;
case BIOCPROMISC:
if (d->bd_bif == 0) {
error = EINVAL;
break;
}
mutex_enter(&d->bd_lock);
if (d->bd_promisc == 0) {
if (d->bd_promisc_handle) {
uintptr_t mph;
mph = d->bd_promisc_handle;
d->bd_promisc_handle = 0;
mutex_exit(&d->bd_lock);
MBPF_PROMISC_REMOVE(&d->bd_mac, mph);
mutex_enter(&d->bd_lock);
}
d->bd_promisc_flags = MAC_PROMISC_FLAGS_NO_COPY;
error = MBPF_PROMISC_ADD(&d->bd_mac,
d->bd_mcip, MAC_CLIENT_PROMISC_ALL, d,
&d->bd_promisc_handle, d->bd_promisc_flags);
if (error == 0)
d->bd_promisc = 1;
}
mutex_exit(&d->bd_lock);
break;
case BIOCGDLT:
if (d->bd_bif == 0)
error = EINVAL;
else
error = copyout(&d->bd_dlt, (void *)addr,
sizeof (d->bd_dlt));
break;
case BIOCGDLTLIST:
if (d->bd_bif == 0) {
error = EINVAL;
} else {
struct bpf_dltlist list;
if (copyin((void *)addr, &list, sizeof (list)) != 0) {
error = EFAULT;
break;
}
error = bpf_getdltlist(d, &list);
if ((error == 0) &&
copyout(&list, (void *)addr, sizeof (list)) != 0)
error = EFAULT;
}
break;
case BIOCSDLT:
error = bpf_setdlt(d, (void *)addr);
break;
case BIOCGETIF:
if (copyin((void *)addr, &ifreq, sizeof (ifreq)) != 0) {
error = EFAULT;
break;
}
error = bpf_ifname(d, ifreq.ifr_name, sizeof (ifreq.ifr_name));
if ((error == 0) &&
copyout(&ifreq, (void *)addr, sizeof (ifreq)) != 0) {
error = EFAULT;
break;
}
break;
case BIOCSETIF:
if (copyin((void *)addr, &ifreq, sizeof (ifreq)) != 0) {
error = EFAULT;
break;
}
error = bpf_setif(d, ifreq.ifr_name, sizeof (ifreq.ifr_name));
break;
case BIOCGETLIF:
if (copyin((void *)addr, &lifreq, sizeof (lifreq)) != 0) {
error = EFAULT;
break;
}
error = bpf_ifname(d, lifreq.lifr_name,
sizeof (lifreq.lifr_name));
if ((error == 0) &&
copyout(&lifreq, (void *)addr, sizeof (lifreq)) != 0) {
error = EFAULT;
break;
}
break;
case BIOCSETLIF:
if (copyin((void *)addr, &lifreq, sizeof (lifreq)) != 0) {
error = EFAULT;
break;
}
error = bpf_setif(d, lifreq.lifr_name,
sizeof (lifreq.lifr_name));
break;
#ifdef _SYSCALL32_IMPL
case BIOCSRTIMEOUT32:
{
struct timeval32 tv;
if (copyin((void *)addr, &tv, sizeof (tv)) != 0) {
error = EFAULT;
break;
}
d->bd_rtout = drv_usectohz(tv.tv_sec * 1000000 +
tv.tv_usec);
if ((d->bd_rtout == 0) && (tv.tv_usec != 0))
d->bd_rtout = 1;
break;
}
case BIOCGRTIMEOUT32:
{
struct timeval32 tv;
clock_t ticks;
ticks = drv_hztousec(d->bd_rtout);
tv.tv_sec = ticks / 1000000;
tv.tv_usec = ticks - (tv.tv_sec * 1000000);
error = copyout(&tv, (void *)addr, sizeof (tv));
break;
}
case BIOCGDLTLIST32:
if (d->bd_bif == 0) {
error = EINVAL;
} else {
struct bpf_dltlist32 lst32;
struct bpf_dltlist list;
if (copyin((void *)addr, &lst32, sizeof (lst32)) != 0) {
error = EFAULT;
break;
}
list.bfl_len = lst32.bfl_len;
list.bfl_list = (void *)(uint64_t)lst32.bfl_list;
error = bpf_getdltlist(d, &list);
if (error == 0) {
lst32.bfl_len = list.bfl_len;
if (copyout(&lst32, (void *)addr,
sizeof (lst32)) != 0)
error = EFAULT;
}
}
break;
case BIOCSETF32: {
struct bpf_program32 prog32;
if (ddi_copyin((void *)addr, &prog32, sizeof (prog), mode)) {
error = EFAULT;
break;
}
prog.bf_len = prog32.bf_len;
prog.bf_insns = (void *)(uint64_t)prog32.bf_insns;
error = bpf_setf(d, &prog);
break;
}
#endif
case BIOCSRTIMEOUT:
{
struct timeval tv;
if (copyin((void *)addr, &tv, sizeof (tv)) != 0) {
error = EFAULT;
break;
}
d->bd_rtout = drv_usectohz(tv.tv_sec * 1000000 +
tv.tv_usec);
if ((d->bd_rtout == 0) && (tv.tv_usec != 0))
d->bd_rtout = 1;
break;
}
case BIOCGRTIMEOUT:
{
struct timeval tv;
clock_t ticks;
ticks = drv_hztousec(d->bd_rtout);
tv.tv_sec = ticks / 1000000;
tv.tv_usec = ticks - (tv.tv_sec * 1000000);
if (copyout(&tv, (void *)addr, sizeof (tv)) != 0)
error = EFAULT;
break;
}
case BIOCGSTATS:
{
struct bpf_stat bs;
bs.bs_recv = d->bd_rcount;
bs.bs_drop = d->bd_dcount;
bs.bs_capt = d->bd_ccount;
if (copyout(&bs, (void *)addr, sizeof (bs)) != 0)
error = EFAULT;
break;
}
case BIOCIMMEDIATE:
if (copyin((void *)addr, &d->bd_immediate,
sizeof (d->bd_immediate)) != 0)
error = EFAULT;
break;
case BIOCVERSION:
{
struct bpf_version bv;
bv.bv_major = BPF_MAJOR_VERSION;
bv.bv_minor = BPF_MINOR_VERSION;
if (copyout(&bv, (void *)addr, sizeof (bv)) != 0)
error = EFAULT;
break;
}
case BIOCGHDRCMPLT:
if (copyout(&d->bd_hdrcmplt, (void *)addr,
sizeof (d->bd_hdrcmplt)) != 0)
error = EFAULT;
break;
case BIOCSHDRCMPLT:
if (copyin((void *)addr, &d->bd_hdrcmplt,
sizeof (d->bd_hdrcmplt)) != 0)
error = EFAULT;
break;
case BIOCGSEESENT:
if (copyout(&d->bd_seesent, (void *)addr,
sizeof (d->bd_seesent)) != 0)
error = EFAULT;
break;
case BIOCSSEESENT:
if (copyin((void *)addr, &d->bd_seesent,
sizeof (d->bd_seesent)) != 0)
error = EFAULT;
break;
case FIONBIO:
if (copyin((void *)addr, &d->bd_nonblock,
sizeof (d->bd_nonblock)) != 0)
error = EFAULT;
break;
}
return (error);
}
int
bpf_setf(struct bpf_d *d, struct bpf_program *fp)
{
struct bpf_insn *fcode, *old;
uint_t flen, size;
size_t oldsize;
if (fp->bf_insns == 0) {
if (fp->bf_len != 0)
return (EINVAL);
mutex_enter(&d->bd_lock);
old = d->bd_filter;
oldsize = d->bd_filter_size;
d->bd_filter = 0;
d->bd_filter_size = 0;
reset_d(d);
mutex_exit(&d->bd_lock);
if (old != 0)
kmem_free(old, oldsize);
return (0);
}
flen = fp->bf_len;
if (flen > BPF_MAXINSNS)
return (EINVAL);
size = flen * sizeof (*fp->bf_insns);
fcode = kmem_alloc(size, KM_SLEEP);
if (copyin(fp->bf_insns, fcode, size) != 0)
return (EFAULT);
if (bpf_validate(fcode, (int)flen)) {
mutex_enter(&d->bd_lock);
old = d->bd_filter;
oldsize = d->bd_filter_size;
d->bd_filter = fcode;
d->bd_filter_size = size;
reset_d(d);
mutex_exit(&d->bd_lock);
if (old != 0)
kmem_free(old, oldsize);
return (0);
}
kmem_free(fcode, size);
return (EINVAL);
}
static int
bpf_setif(struct bpf_d *d, char *ifname, int namesize)
{
int unit_seen;
int error = 0;
char *cp;
int i;
unit_seen = 0;
cp = ifname;
cp[namesize - 1] = '\0';
while (*cp++)
if (*cp >= '0' && *cp <= '9')
unit_seen = 1;
if (!unit_seen) {
for (i = 0; i < (namesize - 1); ++i) {
if ((ifname[i] >= 'a' && ifname[i] <= 'z') ||
(ifname[i] >= 'A' && ifname[i] <= 'Z'))
continue;
ifname[i] = '0';
}
}
mutex_enter(&d->bd_lock);
while (d->bd_inuse != 0) {
d->bd_waiting++;
if (cv_wait_sig(&d->bd_wait, &d->bd_lock) <= 0) {
d->bd_waiting--;
mutex_exit(&d->bd_lock);
return (EINTR);
}
d->bd_waiting--;
}
d->bd_inuse = -1;
mutex_exit(&d->bd_lock);
if (d->bd_sbuf == 0)
error = bpf_allocbufs(d);
if (error == 0) {
mutex_enter(&d->bd_lock);
if (d->bd_bif)
bpf_detachd(d);
error = bpf_attachd(d, ifname, -1);
reset_d(d);
d->bd_inuse = 0;
if (d->bd_waiting != 0)
cv_signal(&d->bd_wait);
mutex_exit(&d->bd_lock);
return (error);
}
mutex_enter(&d->bd_lock);
d->bd_inuse = 0;
if (d->bd_waiting != 0)
cv_signal(&d->bd_wait);
mutex_exit(&d->bd_lock);
return (bpf_provider_tickle(ifname, d->bd_zone));
}
static int
bpf_ifname(struct bpf_d *d, char *buffer, int bufsize)
{
mutex_enter(&d->bd_lock);
if (d->bd_bif == 0) {
mutex_exit(&d->bd_lock);
return (EINVAL);
}
(void) strlcpy(buffer, d->bd_ifname, bufsize);
mutex_exit(&d->bd_lock);
return (0);
}
int
bpfchpoll(dev_t dev, short events, int anyyet, short *reventsp,
struct pollhead **phpp)
{
struct bpf_d *d = bpf_dev_get(getminor(dev));
if (events & POLLET) {
return (EPERM);
}
if (events & (POLLIN | POLLRDNORM)) {
mutex_enter(&d->bd_lock);
if (d->bd_hlen != 0 ||
((d->bd_immediate || d->bd_state == BPF_TIMED_OUT) &&
d->bd_slen != 0)) {
*reventsp |= events & (POLLIN | POLLRDNORM);
} else {
*reventsp = 0;
if (d->bd_rtout > 0 && d->bd_state == BPF_IDLE) {
bpf_clear_timeout(d);
if (d->bd_callout == 0)
d->bd_callout = timeout(bpf_timed_out,
d, d->bd_rtout);
d->bd_state = BPF_WAITING;
}
}
mutex_exit(&d->bd_lock);
}
return (0);
}
static void *
bpf_mcpy(void *dst_arg, const void *src_arg, size_t len)
{
const mblk_t *m;
uint_t count;
uchar_t *dst;
m = src_arg;
dst = dst_arg;
while (len > 0) {
if (m == NULL)
panic("bpf_mcpy");
count = (uint_t)min(M_LEN(m), len);
(void) memcpy(dst, mtod(m, const void *), count);
m = m->b_cont;
dst += count;
len -= count;
}
return (dst_arg);
}
static inline void
bpf_deliver(struct bpf_d *d, cp_fn_t cpfn, void *marg, uint_t pktlen,
uint_t buflen, boolean_t issent)
{
struct timeval tv;
uint_t slen;
if (!d->bd_seesent && issent)
return;
mutex_enter(&d->bd_lock);
slen = bpf_filter(d->bd_filter, marg, pktlen, buflen);
DTRACE_PROBE5(bpf__packet, struct bpf_if *, d->bd_bif,
struct bpf_d *, d, void *, marg, uint_t, pktlen, uint_t, slen);
d->bd_rcount++;
ks_stats.kp_receive.value.ui64++;
if (slen != 0) {
uniqtime(&tv);
catchpacket(d, marg, pktlen, slen, cpfn, &tv);
}
mutex_exit(&d->bd_lock);
}
void
bpf_mtap(void *arg, mac_resource_handle_t mrh, mblk_t *m, boolean_t issent)
{
cp_fn_t cpfn;
struct bpf_d *d = arg;
uint_t pktlen, buflen;
void *marg;
pktlen = msgdsize(m);
if (pktlen == M_LEN(m)) {
cpfn = (cp_fn_t)memcpy;
marg = mtod(m, void *);
buflen = pktlen;
} else {
cpfn = bpf_mcpy;
marg = m;
buflen = 0;
}
bpf_deliver(d, cpfn, marg, pktlen, buflen, issent);
}
void
bpf_itap(void *arg, mblk_t *m, boolean_t issent, uint_t length)
{
hook_pkt_observe_t *hdr;
struct bpf_d *d = arg;
hdr = (hook_pkt_observe_t *)m->b_rptr;
if (ntohl(hdr->hpo_ifindex) != d->bd_linkid)
return;
bpf_deliver(d, bpf_mcpy, m, length, 0, issent);
}
static void
catchpacket(struct bpf_d *d, uchar_t *pkt, uint_t pktlen, uint_t snaplen,
cp_fn_t cpfn, struct timeval *tv)
{
struct bpf_hdr *hp;
int totlen, curlen;
int hdrlen = d->bd_hdrlen;
int do_wakeup = 0;
++d->bd_ccount;
ks_stats.kp_capture.value.ui64++;
totlen = hdrlen + min(snaplen, pktlen);
if (totlen > d->bd_bufsize)
totlen = d->bd_bufsize;
curlen = BPF_WORDALIGN(d->bd_slen);
if (curlen + totlen > d->bd_bufsize) {
if (d->bd_fbuf == 0) {
++d->bd_dcount;
ks_stats.kp_dropped.value.ui64++;
return;
}
ROTATE_BUFFERS(d);
do_wakeup = 1;
curlen = 0;
} else if (d->bd_immediate || d->bd_state == BPF_TIMED_OUT) {
do_wakeup = 1;
}
hp = (struct bpf_hdr *)((char *)d->bd_sbuf + curlen);
hp->bh_tstamp.tv_sec = tv->tv_sec;
hp->bh_tstamp.tv_usec = tv->tv_usec;
hp->bh_datalen = pktlen;
hp->bh_hdrlen = (uint16_t)hdrlen;
(*cpfn)((uchar_t *)hp + hdrlen, pkt,
(hp->bh_caplen = totlen - hdrlen));
d->bd_slen = curlen + totlen;
if (do_wakeup)
bpf_wakeup(d);
}
static int
bpf_allocbufs(struct bpf_d *d)
{
d->bd_fbuf = kmem_zalloc(d->bd_bufsize, KM_NOSLEEP);
if (!d->bd_fbuf)
return (ENOBUFS);
d->bd_sbuf = kmem_zalloc(d->bd_bufsize, KM_NOSLEEP);
if (!d->bd_sbuf) {
kmem_free(d->bd_fbuf, d->bd_bufsize);
return (ENOBUFS);
}
d->bd_slen = 0;
d->bd_hlen = 0;
return (0);
}
static void
bpf_freed(struct bpf_d *d)
{
if (d->bd_sbuf != 0) {
kmem_free(d->bd_sbuf, d->bd_bufsize);
if (d->bd_hbuf != 0)
kmem_free(d->bd_hbuf, d->bd_bufsize);
if (d->bd_fbuf != 0)
kmem_free(d->bd_fbuf, d->bd_bufsize);
}
if (d->bd_filter)
kmem_free(d->bd_filter, d->bd_filter_size);
}
static int
bpf_getdltlist(struct bpf_d *d, struct bpf_dltlist *listp)
{
bpf_provider_list_t *bp;
bpf_provider_t *bpr;
zoneid_t zoneid;
uintptr_t mcip;
uint_t nicdlt;
uintptr_t mh;
int error;
int n;
n = 0;
mh = 0;
mcip = 0;
error = 0;
mutex_enter(&d->bd_lock);
LIST_FOREACH(bp, &bpf_providers, bpl_next) {
bpr = bp->bpl_what;
error = MBPF_OPEN(bpr, d->bd_ifname, &mh, d->bd_zone);
if (error != 0)
goto next;
error = MBPF_CLIENT_OPEN(bpr, mh, &mcip);
if (error != 0)
goto next;
error = MBPF_GET_ZONE(bpr, mh, &zoneid);
if (error != 0)
goto next;
if (d->bd_zone != GLOBAL_ZONEID &&
d->bd_zone != zoneid)
goto next;
error = MBPF_GET_DLT(bpr, mh, &nicdlt);
if (error != 0)
goto next;
nicdlt = bpf_dl_to_dlt(nicdlt);
if (listp->bfl_list != NULL) {
if (n >= listp->bfl_len) {
MBPF_CLIENT_CLOSE(bpr, mcip);
MBPF_CLOSE(bpr, mh);
break;
}
d->bd_inuse++;
mutex_exit(&d->bd_lock);
if (copyout(&nicdlt,
listp->bfl_list + n, sizeof (uint_t)) != 0)
error = EFAULT;
mutex_enter(&d->bd_lock);
if (error != 0)
break;
d->bd_inuse--;
}
n++;
next:
if (mcip != 0) {
MBPF_CLIENT_CLOSE(bpr, mcip);
mcip = 0;
}
if (mh != 0) {
MBPF_CLOSE(bpr, mh);
mh = 0;
}
}
mutex_exit(&d->bd_lock);
if ((error != EFAULT) && (n > 0))
error = 0;
listp->bfl_len = n;
return (error);
}
static int
bpf_setdlt(struct bpf_d *d, void *addr)
{
char ifname[LIFNAMSIZ+1];
zoneid_t niczone;
int error;
int dlt;
if (copyin(addr, &dlt, sizeof (dlt)) != 0)
return (EFAULT);
mutex_enter(&d->bd_lock);
if (d->bd_bif == 0) {
mutex_exit(&d->bd_lock);
return (EINVAL);
}
if (d->bd_dlt == dlt) {
mutex_exit(&d->bd_lock);
return (0);
}
error = MBPF_GET_ZONE(&d->bd_mac, d->bd_bif, &niczone);
if (error != 0) {
mutex_exit(&d->bd_lock);
return (error);
}
if ((d->bd_zone != GLOBAL_ZONEID) && (dlt != DLT_IPNET) &&
(niczone != d->bd_zone)) {
mutex_exit(&d->bd_lock);
return (EINVAL);
}
(void) strlcpy(ifname, d->bd_ifname, sizeof (ifname));
d->bd_inuse = -1;
bpf_detachd(d);
error = bpf_attachd(d, ifname, dlt);
reset_d(d);
d->bd_inuse = 0;
mutex_exit(&d->bd_lock);
return (error);
}
static void
bpf_clear_timeout(struct bpf_d *d)
{
timeout_id_t tid = d->bd_callout;
d->bd_callout = 0;
d->bd_inuse++;
if (tid != 0) {
mutex_exit(&d->bd_lock);
(void) untimeout(tid);
mutex_enter(&d->bd_lock);
}
d->bd_inuse--;
}
static struct bpf_d *
bpf_dev_find(minor_t minor)
{
struct bpf_d *d = NULL;
(void) mod_hash_find(bpf_hash, (mod_hash_key_t)(uintptr_t)minor,
(mod_hash_val_t *)&d);
return (d);
}
static void
bpf_dev_add(struct bpf_d *d)
{
(void) mod_hash_insert(bpf_hash, (mod_hash_key_t)(uintptr_t)d->bd_dev,
(mod_hash_val_t)d);
}
static void
bpf_dev_remove(struct bpf_d *d)
{
struct bpf_d *stor;
(void) mod_hash_remove(bpf_hash, (mod_hash_key_t)(uintptr_t)d->bd_dev,
(mod_hash_val_t *)&stor);
ASSERT(stor == d);
}
static struct bpf_d *
bpf_dev_get(minor_t minor)
{
struct bpf_d *d = NULL;
(void) mod_hash_find(bpf_hash, (mod_hash_key_t)(uintptr_t)minor,
(mod_hash_val_t *)&d);
ASSERT(d != NULL);
return (d);
}