summaryrefslogtreecommitdiffstats
path: root/contrib/libpcap/pcap-linux.c
diff options
context:
space:
mode:
authorfenner <fenner@FreeBSD.org>2002-06-21 01:36:27 +0000
committerfenner <fenner@FreeBSD.org>2002-06-21 01:36:27 +0000
commit1e8ea467791f99f6068888787c27fd8b6b923d2c (patch)
tree4bf19d614b784f03cdea171c65cf7b2e9f63715c /contrib/libpcap/pcap-linux.c
parenta6bce8883c0f9dd7fee0eb03667f57b40b1d9dab (diff)
downloadFreeBSD-src-1e8ea467791f99f6068888787c27fd8b6b923d2c.zip
FreeBSD-src-1e8ea467791f99f6068888787c27fd8b6b923d2c.tar.gz
Import libpcap 0.7.1, from
http://www.tcpdump.org/release/libpcap-0.7.1.tar.gz
Diffstat (limited to 'contrib/libpcap/pcap-linux.c')
-rw-r--r--contrib/libpcap/pcap-linux.c735
1 files changed, 570 insertions, 165 deletions
diff --git a/contrib/libpcap/pcap-linux.c b/contrib/libpcap/pcap-linux.c
index 1199f8f..179cbc0 100644
--- a/contrib/libpcap/pcap-linux.c
+++ b/contrib/libpcap/pcap-linux.c
@@ -26,7 +26,7 @@
*/
#ifndef lint
static const char rcsid[] =
- "@(#) $Header: /tcpdump/master/libpcap/pcap-linux.c,v 1.51.2.3 2001/01/18 03:59:56 guy Exp $ (LBL)";
+ "@(#) $Header: /tcpdump/master/libpcap/pcap-linux.c,v 1.73 2001/12/10 07:14:16 guy Exp $ (LBL)";
#endif
/*
@@ -51,6 +51,24 @@ static const char rcsid[] =
* do, if another socket also requested promiscuous mode between
* the time when we opened the socket and the time when we close
* the socket.
+ *
+ * - MSG_TRUNC isn't supported, so you can't specify that "recvfrom()"
+ * return the amount of data that you could have read, rather than
+ * the amount that was returned, so we can't just allocate a buffer
+ * whose size is the snapshot length and pass the snapshot length
+ * as the byte count, and also pass MSG_TRUNC, so that the return
+ * value tells us how long the packet was on the wire.
+ *
+ * This means that, if we want to get the actual size of the packet,
+ * so we can return it in the "len" field of the packet header,
+ * we have to read the entire packet, not just the part that fits
+ * within the snapshot length, and thus waste CPU time copying data
+ * from the kernel that our caller won't see.
+ *
+ * We have to get the actual size, and supply it in "len", because
+ * otherwise, the IP dissector in tcpdump, for example, will complain
+ * about "truncated-ip", as the packet will appear to have been
+ * shorter, on the wire, than the IP header said it should have been.
*/
@@ -74,37 +92,38 @@ static const char rcsid[] =
#include <linux/if_ether.h>
#include <net/if_arp.h>
-#ifdef HAVE_NETPACKET_PACKET_H
-# include <netpacket/packet.h>
-
- /*
- * We assume this means we really do have PF_PACKET sockets.
- */
-# define HAVE_PF_PACKET_SOCKETS
-#else
- /*
- * Oh, joy. Some Linux distributions have 2.2 or later kernels and
- * libc5. On at least one of those systems (Slackware 4.0), it
- * appears that "/usr/include/sys/socket.h" includes <linux/socket.h>,
- * which means it picks up all the AF_, PF_, and SO_ definitions
- * appropriate for the current kernel; however, it also appears that
- * they did not see fit to provide a "/usr/include/netpacket/packet.h"
- * file.
- *
- * However, you should be able to get the right definitions by including
- * <linux/if_packet.h>.
- *
- * So if this system has PF_PACKET defined but doesn't have the
- * <netpacket/packet.h> header file, we include <linux/if_packet.h>
- * instead.
- */
-# ifdef PF_PACKET
-# include <linux/if_packet.h>
+/*
+ * If PF_PACKET is defined, we can use {SOCK_RAW,SOCK_DGRAM}/PF_PACKET
+ * sockets rather than SOCK_PACKET sockets.
+ *
+ * To use them, we include <linux/if_packet.h> rather than
+ * <netpacket/packet.h>; we do so because
+ *
+ * some Linux distributions (e.g., Slackware 4.0) have 2.2 or
+ * later kernels and libc5, and don't provide a <netpacket/packet.h>
+ * file;
+ *
+ * not all versions of glibc2 have a <netpacket/packet.h> file
+ * that defines stuff needed for some of the 2.4-or-later-kernel
+ * features, so if the system has a 2.4 or later kernel, we
+ * still can't use those features.
+ *
+ * We're already including a number of other <linux/XXX.h> headers, and
+ * this code is Linux-specific (no other OS has PF_PACKET sockets as
+ * a raw packet capture mechanism), so it's not as if you gain any
+ * useful portability by using <netpacket/packet.h>
+ *
+ * XXX - should we just include <linux/if_packet.h> even if PF_PACKET
+ * isn't defined? It only defines one data structure in 2.0.x, so
+ * it shouldn't cause any problems.
+ */
+#ifdef PF_PACKET
+# include <linux/if_packet.h>
/*
- * However, on at least some Linux distributions (for example, Red Hat
- * 5.2), there's no <netpacket/packet.h> file, but PF_PACKET is defined
- * if you include <sys/socket.h>, but <linux/if_packet.h> doesn't define
+ * On at least some Linux distributions (for example, Red Hat 5.2),
+ * there's no <netpacket/packet.h> file, but PF_PACKET is defined if
+ * you include <sys/socket.h>, but <linux/if_packet.h> doesn't define
* any of the PF_PACKET stuff such as "struct sockaddr_ll" or any of
* the PACKET_xxx stuff.
*
@@ -114,8 +133,7 @@ static const char rcsid[] =
# ifdef PACKET_HOST
# define HAVE_PF_PACKET_SOCKETS
# endif /* PACKET_HOST */
-# endif /* PF_PACKET */
-#endif /* HAVE_NETPACKET_PACKET_H */
+#endif /* PF_PACKET */
#ifdef SO_ATTACH_FILTER
#include <linux/types.h>
@@ -127,7 +145,15 @@ typedef int socklen_t;
#endif
#ifndef MSG_TRUNC
-#define MSG_TRUNC 0
+/*
+ * This is being compiled on a system that lacks MSG_TRUNC; define it
+ * with the value it has in the 2.2 and later kernels, so that, on
+ * those kernels, when we pass it in the flags argument to "recvfrom()"
+ * we're passing the right value and thus get the MSG_TRUNC behavior
+ * we want. (We don't get that behavior on 2.0[.x] kernels, because
+ * they didn't support MSG_TRUNC.)
+ */
+#define MSG_TRUNC 0x20
#endif
#define MAX_LINKHEADER_SIZE 256
@@ -142,7 +168,7 @@ typedef int socklen_t;
/*
* Prototypes for internal functions
*/
-static int map_arphrd_to_dlt(int arptype );
+static void map_arphrd_to_dlt(pcap_t *, int);
static int live_open_old(pcap_t *, char *, int, int, char *);
static int live_open_new(pcap_t *, char *, int, int, char *);
static int pcap_read_packet(pcap_t *, pcap_handler, u_char *);
@@ -163,6 +189,13 @@ static int iface_bind_old(int fd, const char *device, char *ebuf);
#ifdef SO_ATTACH_FILTER
static int fix_program(pcap_t *handle, struct sock_fprog *fcode);
static int fix_offset(struct bpf_insn *p);
+static int set_kernel_filter(pcap_t *handle, struct sock_fprog *fcode);
+static int reset_kernel_filter(pcap_t *handle);
+
+static struct sock_filter total_insn
+ = BPF_STMT(BPF_RET | BPF_K, 0);
+static struct sock_fprog total_fcode
+ = { 1, &total_insn };
#endif
/*
@@ -178,9 +211,13 @@ static int fix_offset(struct bpf_insn *p);
pcap_t *
pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
{
+ pcap_t *handle;
+ int mtu;
+ struct utsname utsname;
+
/* Allocate a handle for this session. */
- pcap_t *handle = malloc(sizeof(*handle));
+ handle = malloc(sizeof(*handle));
if (handle == NULL) {
snprintf(ebuf, PCAP_ERRBUF_SIZE, "malloc: %s",
pcap_strerror(errno));
@@ -200,6 +237,13 @@ pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
if (!device || strcmp(device, "any") == 0) {
device = NULL;
handle->md.device = strdup("any");
+ if (promisc) {
+ promisc = 0;
+ /* Just a warning. */
+ snprintf(ebuf, PCAP_ERRBUF_SIZE,
+ "Promiscuous mode not supported on the \"any\" device");
+ }
+
} else
handle->md.device = strdup(device);
@@ -234,6 +278,93 @@ pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
return NULL;
}
+ /*
+ * Compute the buffer size.
+ *
+ * If we're using SOCK_PACKET, this might be a 2.0[.x] kernel,
+ * and might require special handling - check.
+ */
+ if (handle->md.sock_packet && (uname(&utsname) < 0 ||
+ strncmp(utsname.release, "2.0", 3) == 0)) {
+ /*
+ * We're using a SOCK_PACKET structure, and either
+ * we couldn't find out what kernel release this is,
+ * or it's a 2.0[.x] kernel.
+ *
+ * In the 2.0[.x] kernel, a "recvfrom()" on
+ * a SOCK_PACKET socket, with MSG_TRUNC set, will
+ * return the number of bytes read, so if we pass
+ * a length based on the snapshot length, it'll
+ * return the number of bytes from the packet
+ * copied to userland, not the actual length
+ * of the packet.
+ *
+ * This means that, for example, the IP dissector
+ * in tcpdump will get handed a packet length less
+ * than the length in the IP header, and will
+ * complain about "truncated-ip".
+ *
+ * So we don't bother trying to copy from the
+ * kernel only the bytes in which we're interested,
+ * but instead copy them all, just as the older
+ * versions of libpcap for Linux did.
+ *
+ * The buffer therefore needs to be big enough to
+ * hold the largest packet we can get from this
+ * device. Unfortunately, we can't get the MRU
+ * of the network; we can only get the MTU. The
+ * MTU may be too small, in which case a packet larger
+ * than the buffer size will be truncated *and* we
+ * won't get the actual packet size.
+ *
+ * However, if the snapshot length is larger than
+ * the buffer size based on the MTU, we use the
+ * snapshot length as the buffer size, instead;
+ * this means that with a sufficiently large snapshot
+ * length we won't artificially truncate packets
+ * to the MTU-based size.
+ *
+ * This mess just one of many problems with packet
+ * capture on 2.0[.x] kernels; you really want a
+ * 2.2[.x] or later kernel if you want packet capture
+ * to work well.
+ */
+ mtu = iface_get_mtu(handle->fd, device, ebuf);
+ if (mtu == -1) {
+ close(handle->fd);
+ free(handle->md.device);
+ free(handle);
+ return NULL;
+ }
+ handle->bufsize = MAX_LINKHEADER_SIZE + mtu;
+ if (handle->bufsize < handle->snapshot)
+ handle->bufsize = handle->snapshot;
+ } else {
+ /*
+ * This is a 2.2[.x] or later kernel (we know that
+ * either because we're not using a SOCK_PACKET
+ * socket - PF_PACKET is supported only in 2.2
+ * and later kernels - or because we checked the
+ * kernel version).
+ *
+ * We can safely pass "recvfrom()" a byte count
+ * based on the snapshot length.
+ */
+ handle->bufsize = handle->snapshot;
+ }
+
+ /* Allocate the buffer */
+
+ handle->buffer = malloc(handle->bufsize + handle->offset);
+ if (!handle->buffer) {
+ snprintf(ebuf, PCAP_ERRBUF_SIZE,
+ "malloc: %s", pcap_strerror(errno));
+ close(handle->fd);
+ free(handle->md.device);
+ free(handle);
+ return NULL;
+ }
+
return handle;
}
@@ -260,6 +391,7 @@ pcap_read(pcap_t *handle, int max_packets, pcap_handler callback, u_char *user)
static int
pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
{
+ u_char *bp;
int offset;
#ifdef HAVE_PF_PACKET_SOCKETS
struct sockaddr_ll from;
@@ -290,11 +422,12 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
/* Receive a single packet from the kernel */
+ bp = handle->buffer + handle->offset;
do {
fromlen = sizeof(from);
packet_len = recvfrom(
- handle->fd, handle->buffer + offset + handle->offset,
- handle->md.readlen - offset, MSG_TRUNC,
+ handle->fd, bp + offset,
+ handle->bufsize - offset, MSG_TRUNC,
(struct sockaddr *) &from, &fromlen);
} while (packet_len == -1 && errno == EINTR);
@@ -337,7 +470,7 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
*/
packet_len += SLL_HDR_LEN;
- hdrp = (struct sll_header *)handle->buffer;
+ hdrp = (struct sll_header *)bp;
/*
* Map the PACKET_ value to a LINUX_SLL_ value; we
@@ -422,7 +555,7 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
/* Run the packet filter if not using kernel filter */
if (!handle->md.use_bpf && handle->fcode.bf_insns) {
- if (bpf_filter(handle->fcode.bf_insns, handle->buffer,
+ if (bpf_filter(handle->fcode.bf_insns, bp,
packet_len, caplen) == 0)
{
/* rejected by filter */
@@ -440,20 +573,139 @@ pcap_read_packet(pcap_t *handle, pcap_handler callback, u_char *userdata)
pcap_header.caplen = caplen;
pcap_header.len = packet_len;
- /* Call the user supplied callback function */
+ /*
+ * Count the packet.
+ *
+ * Arguably, we should count them before we check the filter,
+ * as on many other platforms "ps_recv" counts packets
+ * handed to the filter rather than packets that passed
+ * the filter, but if filtering is done in the kernel, we
+ * can't get a count of packets that passed the filter,
+ * and that would mean the meaning of "ps_recv" wouldn't
+ * be the same on all Linux systems.
+ *
+ * XXX - it's not the same on all systems in any case;
+ * ideally, we should have a "get the statistics" call
+ * that supplies more counts and indicates which of them
+ * it supplies, so that we supply a count of packets
+ * handed to the filter only on platforms where that
+ * information is available.
+ *
+ * We count them here even if we can get the packet count
+ * from the kernel, as we can only determine at run time
+ * whether we'll be able to get it from the kernel (if
+ * HAVE_TPACKET_STATS isn't defined, we can't get it from
+ * the kernel, but if it is defined, the library might
+ * have been built with a 2.4 or later kernel, but we
+ * might be running on a 2.2[.x] kernel without Alexey
+ * Kuznetzov's turbopacket patches, and thus the kernel
+ * might not be able to supply those statistics). We
+ * could, I guess, try, when opening the socket, to get
+ * the statistics, and if we can not increment the count
+ * here, but it's not clear that always incrementing
+ * the count is more expensive than always testing a flag
+ * in memory.
+ */
handle->md.stat.ps_recv++;
- callback(userdata, &pcap_header, handle->buffer + handle->offset);
+
+ /* Call the user supplied callback function */
+ callback(userdata, &pcap_header, bp);
return 1;
}
/*
* Get the statistics for the given packet capture handle.
- * FIXME: Currently does not report the number of dropped packets.
+ * Reports the number of dropped packets iff the kernel supports
+ * the PACKET_STATISTICS "getsockopt()" argument (2.4 and later
+ * kernels, and 2.2[.x] kernels with Alexey Kuznetzov's turbopacket
+ * patches); otherwise, that information isn't available, and we lie
+ * and report 0 as the count of dropped packets.
*/
int
pcap_stats(pcap_t *handle, struct pcap_stat *stats)
{
+#ifdef HAVE_TPACKET_STATS
+ struct tpacket_stats kstats;
+ socklen_t len = sizeof (struct tpacket_stats);
+
+ /*
+ * Try to get the packet counts from the kernel.
+ */
+ if (getsockopt(handle->fd, SOL_PACKET, PACKET_STATISTICS,
+ &kstats, &len) > -1) {
+ /*
+ * In "linux/net/packet/af_packet.c", at least in the
+ * 2.4.9 kernel, "tp_packets" is incremented for every
+ * packet that passes the packet filter *and* is
+ * successfully queued on the socket; "tp_drops" is
+ * incremented for every packet dropped because there's
+ * not enough free space in the socket buffer.
+ *
+ * When the statistics are returned for a PACKET_STATISTICS
+ * "getsockopt()" call, "tp_drops" is added to "tp_packets",
+ * so that "tp_packets" counts all packets handed to
+ * the PF_PACKET socket, including packets dropped because
+ * there wasn't room on the socket buffer - but not
+ * including packets that didn't pass the filter.
+ *
+ * In the BSD BPF, the count of received packets is
+ * incremented for every packet handed to BPF, regardless
+ * of whether it passed the filter.
+ *
+ * We can't make "pcap_stats()" work the same on both
+ * platforms, but the best approximation is to return
+ * "tp_packets" as the count of packets and "tp_drops"
+ * as the count of drops.
+ */
+ handle->md.stat.ps_recv = kstats.tp_packets;
+ handle->md.stat.ps_drop = kstats.tp_drops;
+ }
+ else
+ {
+ /*
+ * If the error was EOPNOTSUPP, fall through, so that
+ * if you build the library on a system with
+ * "struct tpacket_stats" and run it on a system
+ * that doesn't, it works as it does if the library
+ * is built on a system without "struct tpacket_stats".
+ */
+ if (errno != EOPNOTSUPP) {
+ snprintf(handle->errbuf, PCAP_ERRBUF_SIZE,
+ "pcap_stats: %s", pcap_strerror(errno));
+ return -1;
+ }
+ }
+#endif
+ /*
+ * On systems where the PACKET_STATISTICS "getsockopt()" argument
+ * is supported on PF_PACKET sockets:
+ *
+ * "ps_recv" counts only packets that *passed* the filter,
+ * not packets that didn't pass the filter. This includes
+ * packets later dropped because we ran out of buffer space.
+ *
+ * "ps_drop" counts packets dropped because we ran out of
+ * buffer space. It doesn't count packets dropped by the
+ * interface driver. It counts only packets that passed
+ * the filter.
+ *
+ * Both statistics include packets not yet read from the
+ * kernel by libpcap, and thus not yet seen by the application.
+ *
+ * On systems where the PACKET_STATISTICS "getsockopt()" argument
+ * is not supported on PF_PACKET sockets:
+ *
+ * "ps_recv" counts only packets that *passed* the filter,
+ * not packets that didn't pass the filter. It does not
+ * count packets dropped because we ran out of buffer
+ * space.
+ *
+ * "ps_drop" is not supported.
+ *
+ * "ps_recv" doesn't include packets not yet read from
+ * the kernel by libpcap.
+ */
*stats = handle->md.stat;
return 0;
}
@@ -557,8 +809,7 @@ pcap_setfilter(pcap_t *handle, struct bpf_program *filter)
}
if (can_filter_in_kernel) {
- if (setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
- &fcode, sizeof(fcode)) == 0)
+ if (set_kernel_filter(handle, &fcode) == 0)
{
/* Installation succeded - using kernel filter. */
handle->md.use_bpf = 1;
@@ -579,6 +830,17 @@ pcap_setfilter(pcap_t *handle, struct bpf_program *filter)
}
/*
+ * If we're not using the kernel filter, get rid of any kernel
+ * filter that might've been there before, e.g. because the
+ * previous filter could work in the kernel, or because some other
+ * code attached a filter to the socket by some means other than
+ * calling "pcap_setfilter()". Otherwise, the kernel filter may
+ * filter out packets that would pass the new userland filter.
+ */
+ if (!handle->md.use_bpf)
+ reset_kernel_filter(handle);
+
+ /*
* Free up the copy of the filter that was made by "fix_program()".
*/
if (fcode.filter != NULL)
@@ -591,37 +853,133 @@ pcap_setfilter(pcap_t *handle, struct bpf_program *filter)
/*
* Linux uses the ARP hardware type to identify the type of an
* interface. pcap uses the DLT_xxx constants for this. This
- * function maps the ARPHRD_xxx constant to an appropriate
- * DLT_xxx constant.
+ * function takes a pointer to a "pcap_t", and an ARPHRD_xxx
+ * constant, as arguments, and sets "handle->linktype" to the
+ * appropriate DLT_XXX constant and sets "handle->offset" to
+ * the appropriate value (to make "handle->offset" plus link-layer
+ * header length be a multiple of 4, so that the link-layer payload
+ * will be aligned on a 4-byte boundary when capturing packets).
+ * (If the offset isn't set here, it'll be 0; add code as appropriate
+ * for cases where it shouldn't be 0.)
*
- * Returns -1 if unable to map the type; we print a message and,
- * if we're using PF_PACKET/SOCK_RAW rather than PF_INET/SOCK_PACKET,
- * we fall back on using PF_PACKET/SOCK_DGRAM.
+ * Sets the link type to -1 if unable to map the type.
*/
-static int map_arphrd_to_dlt(int arptype)
+static void map_arphrd_to_dlt(pcap_t *handle, int arptype)
{
switch (arptype) {
+
case ARPHRD_ETHER:
case ARPHRD_METRICOM:
- case ARPHRD_LOOPBACK: return DLT_EN10MB;
- case ARPHRD_EETHER: return DLT_EN3MB;
- case ARPHRD_AX25: return DLT_AX25;
- case ARPHRD_PRONET: return DLT_PRONET;
- case ARPHRD_CHAOS: return DLT_CHAOS;
+ case ARPHRD_LOOPBACK:
+ handle->linktype = DLT_EN10MB;
+ handle->offset = 2;
+ break;
+
+ case ARPHRD_EETHER:
+ handle->linktype = DLT_EN3MB;
+ break;
+
+ case ARPHRD_AX25:
+ handle->linktype = DLT_AX25;
+ break;
+
+ case ARPHRD_PRONET:
+ handle->linktype = DLT_PRONET;
+ break;
+
+ case ARPHRD_CHAOS:
+ handle->linktype = DLT_CHAOS;
+ break;
+
#ifndef ARPHRD_IEEE802_TR
#define ARPHRD_IEEE802_TR 800 /* From Linux 2.4 */
#endif
case ARPHRD_IEEE802_TR:
- case ARPHRD_IEEE802: return DLT_IEEE802;
- case ARPHRD_ARCNET: return DLT_ARCNET;
- case ARPHRD_FDDI: return DLT_FDDI;
+ case ARPHRD_IEEE802:
+ handle->linktype = DLT_IEEE802;
+ handle->offset = 2;
+ break;
+
+ case ARPHRD_ARCNET:
+ handle->linktype = DLT_ARCNET;
+ break;
+
+ case ARPHRD_FDDI:
+ handle->linktype = DLT_FDDI;
+ handle->offset = 3;
+ break;
#ifndef ARPHRD_ATM /* FIXME: How to #include this? */
#define ARPHRD_ATM 19
#endif
- case ARPHRD_ATM: return DLT_ATM_CLIP;
+ case ARPHRD_ATM:
+ /*
+ * The Classical IP implementation in ATM for Linux
+ * supports both what RFC 1483 calls "LLC Encapsulation",
+ * in which each packet has an LLC header, possibly
+ * with a SNAP header as well, prepended to it, and
+ * what RFC 1483 calls "VC Based Multiplexing", in which
+ * different virtual circuits carry different network
+ * layer protocols, and no header is prepended to packets.
+ *
+ * They both have an ARPHRD_ type of ARPHRD_ATM, so
+ * you can't use the ARPHRD_ type to find out whether
+ * captured packets will have an LLC header, and,
+ * while there's a socket ioctl to *set* the encapsulation
+ * type, there's no ioctl to *get* the encapsulation type.
+ *
+ * This means that
+ *
+ * programs that dissect Linux Classical IP frames
+ * would have to check for an LLC header and,
+ * depending on whether they see one or not, dissect
+ * the frame as LLC-encapsulated or as raw IP (I
+ * don't know whether there's any traffic other than
+ * IP that would show up on the socket, or whether
+ * there's any support for IPv6 in the Linux
+ * Classical IP code);
+ *
+ * filter expressions would have to compile into
+ * code that checks for an LLC header and does
+ * the right thing.
+ *
+ * Both of those are a nuisance - and, at least on systems
+ * that support PF_PACKET sockets, we don't have to put
+ * up with those nuisances; instead, we can just capture
+ * in cooked mode. That's what we'll do.
+ */
+ handle->linktype = DLT_LINUX_SLL;
+ break;
+
+#ifndef ARPHRD_IEEE80211 /* From Linux 2.4.6 */
+#define ARPHRD_IEEE80211 801
+#endif
+ case ARPHRD_IEEE80211:
+ handle->linktype = DLT_IEEE802_11;
+ break;
case ARPHRD_PPP:
+ /*
+ * Some PPP code in the kernel supplies no link-layer
+ * header whatsoever to PF_PACKET sockets; other PPP
+ * code supplies PPP link-layer headers ("syncppp.c");
+ * some PPP code might supply random link-layer
+ * headers (PPP over ISDN - there's code in Ethereal,
+ * for example, to cope with PPP-over-ISDN captures
+ * with which the Ethereal developers have had to cope,
+ * heuristically trying to determine which of the
+ * oddball link-layer headers particular packets have).
+ *
+ * As such, we just punt, and run all PPP interfaces
+ * in cooked mode.
+ */
+ handle->linktype = DLT_LINUX_SLL;
+ break;
+
+ case ARPHRD_HDLC:
+ handle->linktype = DLT_C_HDLC;
+ break;
+
/* Not sure if this is correct for all tunnels, but it
* works for CIPE */
case ARPHRD_TUNNEL:
@@ -632,10 +990,23 @@ static int map_arphrd_to_dlt(int arptype)
case ARPHRD_CSLIP:
case ARPHRD_SLIP6:
case ARPHRD_CSLIP6:
- case ARPHRD_SLIP: return DLT_RAW;
- }
+ case ARPHRD_ADAPT:
+ case ARPHRD_SLIP:
+ /*
+ * XXX - should some of those be mapped to DLT_LINUX_SLL
+ * instead? Should we just map all of them to DLT_LINUX_SLL?
+ */
+ handle->linktype = DLT_RAW;
+ break;
- return -1;
+ case ARPHRD_LOCALTLK:
+ handle->linktype = DLT_LTALK;
+ break;
+
+ default:
+ handle->linktype = -1;
+ break;
+ }
}
/* ===== Functions to interface to the newer kernels ================== */
@@ -650,7 +1021,7 @@ live_open_new(pcap_t *handle, char *device, int promisc,
int to_ms, char *ebuf)
{
#ifdef HAVE_PF_PACKET_SOCKETS
- int sock_fd = -1, device_id, mtu, arptype;
+ int sock_fd = -1, device_id, arptype;
struct packet_mreq mr;
/* One shot loop used for error handling - bail out with break */
@@ -688,6 +1059,12 @@ live_open_new(pcap_t *handle, char *device, int promisc,
handle->md.lo_ifindex = iface_get_id(sock_fd, "lo", ebuf);
/*
+ * Default value for offset to align link-layer payload
+ * on a 4-byte boundary.
+ */
+ handle->offset = 0;
+
+ /*
* What kind of frames do we have to deal with? Fall back
* to cooked mode if we have an unknown interface type.
*/
@@ -699,21 +1076,20 @@ live_open_new(pcap_t *handle, char *device, int promisc,
arptype = iface_get_arptype(sock_fd, device, ebuf);
if (arptype == -1)
break;
- handle->linktype = map_arphrd_to_dlt(arptype);
+ map_arphrd_to_dlt(handle, arptype);
if (handle->linktype == -1 ||
+ handle->linktype == DLT_LINUX_SLL ||
(handle->linktype == DLT_EN10MB &&
(strncmp("isdn", device, 4) == 0 ||
- strncmp("isdY", device, 4) == 0)) ||
- (handle->linktype == DLT_RAW &&
- (strncmp("ippp", device, 4) == 0))) {
+ strncmp("isdY", device, 4) == 0))) {
/*
- * Unknown interface type (-1), or an ISDN
- * device (whose link-layer type we
- * can only determine by using APIs
- * that may be different on different
+ * Unknown interface type (-1), or a
+ * device we explicitly chose to run
+ * in cooked mode (e.g., PPP devices),
+ * or an ISDN device (whose link-layer
+ * type we can only determine by using
+ * APIs that may be different on different
* kernels) - reopen in cooked mode.
- *
- * XXX - do that with DLT_RAW as well?
*/
if (close(sock_fd) == -1) {
snprintf(ebuf, PCAP_ERRBUF_SIZE,
@@ -736,11 +1112,11 @@ live_open_new(pcap_t *handle, char *device, int promisc,
* update "map_arphrd_to_dlt()"
* to handle the new type.
*/
- fprintf(stderr,
- "Warning: arptype %d not "
+ snprintf(ebuf, PCAP_ERRBUF_SIZE,
+ "arptype %d not "
"supported by libpcap - "
"falling back to cooked "
- "socket\n",
+ "socket",
arptype);
}
handle->linktype = DLT_LINUX_SLL;
@@ -793,34 +1169,10 @@ live_open_new(pcap_t *handle, char *device, int promisc,
}
#endif
- /* Compute the buffersize */
-
- mtu = iface_get_mtu(sock_fd, device, ebuf);
- if (mtu == -1)
- break;
- handle->bufsize = MAX_LINKHEADER_SIZE + mtu;
-
- /* Fill in the pcap structure */
+ /* Save the socket FD in the pcap structure */
handle->fd = sock_fd;
- handle->offset = 0;
-
- handle->buffer = malloc(handle->bufsize);
- if (!handle->buffer) {
- snprintf(ebuf, PCAP_ERRBUF_SIZE,
- "malloc: %s", pcap_strerror(errno));
- break;
- }
- /*
- * This is a 2.2 or later kernel, as it has PF_PACKET;
- * "recvfrom()", when passed the MSG_TRUNC flag, will
- * return the actual length of the packet, not the
- * number of bytes from the packet copied to userland,
- * so we can safely pass it a byte count based on the
- * snapshot length.
- */
- handle->md.readlen = handle->snapshot;
return 1;
} while(0);
@@ -995,8 +1347,7 @@ static int
live_open_old(pcap_t *handle, char *device, int promisc,
int to_ms, char *ebuf)
{
- int sock_fd = -1, mtu, arptype;
- struct utsname utsname;
+ int sock_fd = -1, arptype;
struct ifreq ifr;
do {
@@ -1079,24 +1430,22 @@ live_open_old(pcap_t *handle, char *device, int promisc,
}
}
- /* Compute the buffersize */
-
- mtu = iface_get_mtu(sock_fd, device, ebuf);
- if (mtu == -1)
- break;
- handle->bufsize = MAX_LINKHEADER_SIZE + mtu;
- if (handle->bufsize < handle->snapshot)
- handle->bufsize = handle->snapshot;
-
/* All done - fill in the pcap handle */
arptype = iface_get_arptype(sock_fd, device, ebuf);
if (arptype == -1)
break;
+ /* Save the socket FD in the pcap structure */
+
handle->fd = sock_fd;
+
+ /*
+ * Default value for offset to align link-layer payload
+ * on a 4-byte boundary.
+ */
handle->offset = 0;
- handle->linktype = map_arphrd_to_dlt(arptype);
+
/*
* XXX - handle ISDN types here? We can't fall back on
* cooked sockets, so we'd have to figure out from the
@@ -1108,62 +1457,14 @@ live_open_old(pcap_t *handle, char *device, int promisc,
* type that has only an Ethernet packet type as
* a link-layer header.
*/
- if (handle->linktype == -1) {
+ map_arphrd_to_dlt(handle, arptype);
+ if (handle->linktype == -1 ||
+ handle->linktype == DLT_LINUX_SLL) {
snprintf(ebuf, PCAP_ERRBUF_SIZE,
"interface type of %s not supported", device);
break;
}
- handle->buffer = malloc(handle->bufsize);
- if (!handle->buffer) {
- snprintf(ebuf, PCAP_ERRBUF_SIZE,
- "malloc: %s", pcap_strerror(errno));
- break;
- }
- /*
- * This might be a 2.0[.x] kernel - check.
- */
- if (uname(&utsname) < 0 ||
- strncmp(utsname.release, "2.0", 3) == 0) {
- /*
- * Either we couldn't find out what kernel release
- * this is, or it's a 2.0[.x] kernel.
- *
- * In the 2.0[.x] kernel, a "recvfrom()" on
- * a SOCK_PACKET socket, with MSG_TRUNC set, will
- * return the number of bytes read, so if we pass
- * a length based on the snapshot length, it'll
- * return the number of bytes from the packet
- * copied to userland, not the actual length
- * of the packet.
- *
- * This means that, for example, the IP dissector
- * in tcpdump will get handed a packet length less
- * than the length in the IP header, and will
- * complain about "truncated-ip".
- *
- * So we don't bother trying to copy from the
- * kernel only the bytes in which we're interested,
- * but instead copy them all, just as the older
- * versions of libpcap for Linux did.
- *
- * Just one of many problems with packet capture
- * on 2.0[.x] kernels; you really want a 2.2[.x]
- * or later kernel if you want packet capture to
- * work well.
- */
- handle->md.readlen = handle->bufsize;
- } else {
- /*
- * This is a 2.2[.x] or later kernel (although
- * why we're using SOCK_PACKET on such a system
- * is unknown to me).
- *
- * We can safely pass "recvfrom()" a byte count
- * based on the snapshot length.
- */
- handle->md.readlen = handle->snapshot;
- }
return 1;
} while (0);
@@ -1239,7 +1540,7 @@ iface_get_arptype(int fd, const char *device, char *ebuf)
return ifr.ifr_hwaddr.sa_family;
}
-#ifdef HAVE_PF_PACKET_SOCKETS
+#ifdef SO_ATTACH_FILTER
static int
fix_program(pcap_t *handle, struct sock_fprog *fcode)
{
@@ -1363,4 +1664,108 @@ fix_offset(struct bpf_insn *p)
}
return 0;
}
+
+static int
+set_kernel_filter(pcap_t *handle, struct sock_fprog *fcode)
+{
+ int total_filter_on = 0;
+ int save_mode;
+ int ret;
+ int save_errno;
+
+ /*
+ * The socket filter code doesn't discard all packets queued
+ * up on the socket when the filter is changed; this means
+ * that packets that don't match the new filter may show up
+ * after the new filter is put onto the socket, if those
+ * packets haven't yet been read.
+ *
+ * This means, for example, that if you do a tcpdump capture
+ * with a filter, the first few packets in the capture might
+ * be packets that wouldn't have passed the filter.
+ *
+ * We therefore discard all packets queued up on the socket
+ * when setting a kernel filter. (This isn't an issue for
+ * userland filters, as the userland filtering is done after
+ * packets are queued up.)
+ *
+ * To flush those packets, we put the socket in read-only mode,
+ * and read packets from the socket until there are no more to
+ * read.
+ *
+ * In order to keep that from being an infinite loop - i.e.,
+ * to keep more packets from arriving while we're draining
+ * the queue - we put the "total filter", which is a filter
+ * that rejects all packets, onto the socket before draining
+ * the queue.
+ *
+ * This code deliberately ignores any errors, so that you may
+ * get bogus packets if an error occurs, rather than having
+ * the filtering done in userland even if it could have been
+ * done in the kernel.
+ */
+ if (setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ &total_fcode, sizeof(total_fcode)) == 0) {
+ char drain[1];
+
+ /*
+ * Note that we've put the total filter onto the socket.
+ */
+ total_filter_on = 1;
+
+ /*
+ * Save the socket's current mode, and put it in
+ * non-blocking mode; we drain it by reading packets
+ * until we get an error (which we assume is a
+ * "nothing more to be read" error).
+ */
+ save_mode = fcntl(handle->fd, F_GETFL, 0);
+ if (save_mode != -1 &&
+ fcntl(handle->fd, F_SETFL, save_mode | O_NONBLOCK) >= 0) {
+ while (recv(handle->fd, &drain, sizeof drain,
+ MSG_TRUNC) >= 0)
+ ;
+ fcntl(handle->fd, F_SETFL, save_mode);
+ }
+ }
+
+ /*
+ * Now attach the new filter.
+ */
+ ret = setsockopt(handle->fd, SOL_SOCKET, SO_ATTACH_FILTER,
+ fcode, sizeof(*fcode));
+ if (ret == -1 && total_filter_on) {
+ /*
+ * Well, we couldn't set that filter on the socket,
+ * but we could set the total filter on the socket.
+ *
+ * This could, for example, mean that the filter was
+ * too big to put into the kernel, so we'll have to
+ * filter in userland; in any case, we'll be doing
+ * filtering in userland, so we need to remove the
+ * total filter so we see packets.
+ */
+ save_errno = errno;
+
+ /*
+ * XXX - if this fails, we're really screwed;
+ * we have the total filter on the socket,
+ * and it won't come off. What do we do then?
+ */
+ reset_kernel_filter(handle);
+
+ errno = save_errno;
+ }
+ return ret;
+}
+
+static int
+reset_kernel_filter(pcap_t *handle)
+{
+ /* setsockopt() barfs unless it get a dummy parameter */
+ int dummy;
+
+ return setsockopt(handle->fd, SOL_SOCKET, SO_DETACH_FILTER,
+ &dummy, sizeof(dummy));
+}
#endif
OpenPOWER on IntegriCloud