NetBSD/lib/libpcap/pcap-bpf.c
jonathan f0d1f46d5b Do runtime dynamic binary-search for maximum obtainable bpf capture
size (via BIOCSBLEN). Obtained from FreeBSD 4.x; originally from
http://www.tcpdump.org's version of libpcap.

Taken verbatim from the FreeBSD 4.9 version, lint and all, other than
increasing the maximum size from 32Kibyte to an extremely generous
4Mibytes.  We assume root will configure the in-kernel maximum value
sensibly on small-memory machines.
2004-01-20 23:31:20 +00:00

362 lines
8.6 KiB
C

/* $NetBSD: pcap-bpf.c,v 1.11 2004/01/20 23:31:20 jonathan Exp $ */
/*
* Copyright (c) 1993, 1994, 1995, 1996
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that: (1) source code distributions
* retain the above copyright notice and this paragraph in its entirety, (2)
* distributions including binary code include the above copyright notice and
* this paragraph in its entirety in the documentation or other materials
* provided with the distribution, and (3) all advertising materials mentioning
* features or use of this software display the following acknowledgement:
* ``This product includes software developed by the University of California,
* Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
* the University nor the names of its contributors may be used to endorse
* or promote products derived from this software without specific prior
* written permission.
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
*/
#include <sys/cdefs.h>
#ifndef lint
#if 0
static const char rcsid[] =
"@(#) Header: pcap-bpf.c,v 1.29 96/12/31 20:53:40 leres Exp (LBL)";
#else
__RCSID("$NetBSD: pcap-bpf.c,v 1.11 2004/01/20 23:31:20 jonathan Exp $");
#endif
#endif
#include <sys/param.h> /* optionally get BSD define */
#include <sys/time.h>
#include <sys/timeb.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <sys/ioctl.h>
#include <net/if.h>
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "pcap-int.h"
#include "gnuc.h"
#ifdef HAVE_OS_PROTO_H
#include "os-proto.h"
#endif
#include "gencode.h"
int __PCAP_MAX_BUFSIZE = (4*1024*1024);
int
pcap_stats(pcap_t *p, struct pcap_stat *ps)
{
struct bpf_stat s;
if (ioctl(p->fd, BIOCGSTATS, (caddr_t)&s) < 0) {
(void)snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "BIOCGSTATS: %s",
pcap_strerror(errno));
return (-1);
}
ps->ps_recv = s.bs_recv;
ps->ps_drop = s.bs_drop;
return (0);
}
int
pcap_read(pcap_t *p, int cnt, pcap_handler callback, u_char *user)
{
int cc;
int n = 0;
register u_char *bp, *ep;
again:
cc = p->cc;
if (p->cc == 0) {
cc = read(p->fd, (char *)p->buffer, p->bufsize);
if (cc < 0) {
/* Don't choke when we get ptraced */
switch (errno) {
case EINTR:
goto again;
case EWOULDBLOCK:
return (0);
#if defined(sun) && !defined(BSD)
/*
* Due to a SunOS bug, after 2^31 bytes, the kernel
* file offset overflows and read fails with EINVAL.
* The lseek() to 0 will fix things.
*/
case EINVAL:
if (lseek(p->fd, 0L, SEEK_CUR) +
p->bufsize < 0) {
(void)lseek(p->fd, 0L, SEEK_SET);
goto again;
}
/* fall through */
#endif
}
(void)snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "read: %s",
pcap_strerror(errno));
return (-1);
}
bp = p->buffer;
} else
bp = p->bp;
/*
* Loop through each packet.
*/
#define bhp ((struct bpf_hdr *)bp)
ep = bp + cc;
while (bp < ep) {
register int caplen, hdrlen;
caplen = bhp->bh_caplen;
hdrlen = bhp->bh_hdrlen;
/*
* XXX A bpf_hdr matches a pcap_pkthdr.
*/
(*callback)(user, (struct pcap_pkthdr*)bp, bp + hdrlen);
bp += BPF_WORDALIGN(caplen + hdrlen);
if (++n >= cnt && cnt > 0) {
p->bp = bp;
p->cc = ep - bp;
return (n);
}
}
#undef bhp
p->cc = 0;
return (n);
}
static inline int
bpf_open(pcap_t *p, char *errbuf)
{
int fd;
int n = 0;
char device[sizeof "/dev/bpf000"];
/*
* Go through all the minors and find one that isn't in use.
*/
do {
(void)snprintf(device, sizeof device, "/dev/bpf%d", n++);
fd = open(device, O_RDWR);
} while (fd < 0 && errno == EBUSY);
/*
* XXX better message for all minors used
*/
if (fd < 0)
(void)snprintf(errbuf, PCAP_ERRBUF_SIZE, "%s: %s", device,
pcap_strerror(errno));
return (fd);
}
pcap_t *
pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
{
int fd;
struct ifreq ifr;
struct bpf_version bv;
struct bpf_dltlist bdl;
u_int v;
pcap_t *p;
bzero(&bdl, sizeof(bdl));
p = (pcap_t *)malloc(sizeof(*p));
if (p == NULL) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
pcap_strerror(errno));
return (NULL);
}
bzero(p, sizeof(*p));
fd = bpf_open(p, ebuf);
if (fd < 0)
goto bad;
p->fd = fd;
p->snapshot = snaplen;
if (ioctl(fd, BIOCVERSION, (caddr_t)&bv) < 0) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCVERSION: %s",
pcap_strerror(errno));
goto bad;
}
if (bv.bv_major != BPF_MAJOR_VERSION ||
bv.bv_minor < BPF_MINOR_VERSION) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
"kernel bpf filter out of date");
goto bad;
}
/*
* Try finding a good size for the buffer; 32768 (or 4M) may be too
* big, so keep cutting it in half until we find a size
* that works, or run out of sizes to try.
*
* XXX - there should be a user-accessible hook to set the
* initial buffer size.
*/
for (v = __PCAP_MAX_BUFSIZE; v != 0; v >>= 1) {
/* Ignore the return value - this is because the call fails
* on BPF systems that don't have kernel malloc. And if
* the call fails, it's no big deal, we just continue to
* use the standard buffer size.
*/
(void) ioctl(fd, BIOCSBLEN, (caddr_t)&v);
(void)strncpy(ifr.ifr_name, device, sizeof(ifr.ifr_name));
if (ioctl(fd, BIOCSETIF, (caddr_t)&ifr) >= 0)
break; /* that size worked; we're done */
if (errno != ENOBUFS) {
snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCSETIF: %s: %s",
device, pcap_strerror(errno));
goto bad;
}
}
if (v == 0) {
snprintf(ebuf, PCAP_ERRBUF_SIZE,
"BIOCSBLEN: %s: No buffer size worked", device);
goto bad;
}
/* Get the data link layer type. */
if (ioctl(fd, BIOCGDLT, (caddr_t)&v) < 0) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGDLT: %s",
pcap_strerror(errno));
goto bad;
}
#if _BSDI_VERSION - 0 >= 199510
/* The SLIP and PPP link layer header changed in BSD/OS 2.1 */
switch (v) {
case DLT_SLIP:
v = DLT_SLIP_BSDOS;
break;
case DLT_PPP:
v = DLT_PPP_BSDOS;
break;
}
#endif
p->linktype = v;
/*
* We know the default link type -- now determine any additional
* DLTs this interface supports. If this fails, it's not fatal;
* we just don't get to use the feature later.
*/
if (ioctl(fd, BIOCGDLTLIST, (caddr_t) &bdl) == 0) {
bdl.bfl_list = (u_int *) malloc(sizeof(u_int) * bdl.bfl_len);
if (bdl.bfl_list == NULL) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
pcap_strerror(errno));
goto bad;
}
if (ioctl(fd, BIOCGDLTLIST, (caddr_t) &bdl) < 0) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
"BIOCGDLTLIST: %s", pcap_strerror(errno));
goto bad;
}
p->dlt_count = bdl.bfl_len;
p->dlt_list = bdl.bfl_list;
}
/* set timeout */
if (to_ms != 0) {
struct timeval to;
to.tv_sec = to_ms / 1000;
to.tv_usec = (to_ms * 1000) % 1000000;
if (ioctl(p->fd, BIOCSRTIMEOUT, (caddr_t)&to) < 0) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE,
"BIOCSRTIMEOUT: %s", pcap_strerror(errno));
goto bad;
}
}
if (promisc)
/* set promiscuous mode, okay if it fails */
(void)ioctl(p->fd, BIOCPROMISC, NULL);
if (ioctl(fd, BIOCGBLEN, (caddr_t)&v) < 0) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "BIOCGBLEN: %s",
pcap_strerror(errno));
goto bad;
}
p->bufsize = v;
p->buffer = (u_char *)malloc(p->bufsize);
if (p->buffer == NULL) {
(void)snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
pcap_strerror(errno));
goto bad;
}
return (p);
bad:
(void)close(fd);
if (bdl.bfl_list != NULL)
free(bdl.bfl_list);
free(p);
return (NULL);
}
int
pcap_setfilter(pcap_t *p, struct bpf_program *fp)
{
/*
* It looks that BPF code generated by gen_protochain() is not
* compatible with some of kernel BPF code (for example BSD/OS 3.1).
* Take a safer side for now.
*/
if (no_optimize)
p->fcode = *fp;
if (p->sf.rfile != NULL)
p->fcode = *fp;
else if (ioctl(p->fd, BIOCSETF, (caddr_t)fp) < 0) {
(void)snprintf(p->errbuf, PCAP_ERRBUF_SIZE, "BIOCSETF: %s",
pcap_strerror(errno));
return (-1);
}
return (0);
}
int
pcap_set_datalink(pcap_t *p, int dlt)
{
int i;
for (i = 0; i < p->dlt_count; i++)
if (p->dlt_list[i] == dlt)
break;
if (i >= p->dlt_count) {
(void) snprintf(p->errbuf, sizeof(p->errbuf),
"No such DLT as %d", dlt);
return -1;
}
if (ioctl(p->fd, BIOCSDLT, &dlt) == -1) {
(void) snprintf(p->errbuf, sizeof(p->errbuf),
"Cannot set DLT %d: %s", dlt, strerror(errno));
return -1;
}
p->linktype = dlt;
return 0;
}