]> The Tcpdump Group git mirrors - libpcap/blobdiff - pcap-linux.c
We weren't returning a warning if the interface had an ARPHRD_ type we
[libpcap] / pcap-linux.c
index c0f04c24d3e39e7fa252037c6a9279ba1b41a0c7..89446291261fd63565dca3d797b6f71e238fcbb6 100644 (file)
@@ -26,7 +26,7 @@
  */
 #ifndef lint
 static const char rcsid[] =
-    "@(#) $Header: /tcpdump/master/libpcap/pcap-linux.c,v 1.64 2001-08-24 09:27:14 guy Exp $ (LBL)";
+    "@(#) $Header: /tcpdump/master/libpcap/pcap-linux.c,v 1.70 2001-10-25 08:27:18 guy Exp $ (LBL)";
 #endif
 
 /*
@@ -168,7 +168,7 @@ typedef int         socklen_t;
 /*
  * Prototypes for internal functions
  */
-static int map_arphrd_to_dlt(pcap_t *, int);
+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 *);
@@ -189,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
 
 /*
@@ -230,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);
 
@@ -733,8 +747,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;
@@ -754,6 +767,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()".
         */
@@ -776,11 +800,9 @@ pcap_setfilter(pcap_t *handle, struct bpf_program *filter)
  *  (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(pcap_t *handle, int arptype)
+static void map_arphrd_to_dlt(pcap_t *handle, int arptype)
 {
        switch (arptype) {
 
@@ -829,7 +851,42 @@ static int map_arphrd_to_dlt(pcap_t *handle, int arptype)
 #define ARPHRD_ATM 19
 #endif
        case ARPHRD_ATM:
-               handle->linktype = DLT_ATM_CLIP;
+               /*
+                * 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 */
@@ -885,9 +942,9 @@ static int map_arphrd_to_dlt(pcap_t *handle, int arptype)
                break;
 
        default:
-               return -1;
+               handle->linktype = -1;
+               break;
        }
-       return 0;
 }
 
 /* ===== Functions to interface to the newer kernels ================== */
@@ -957,7 +1014,8 @@ live_open_new(pcap_t *handle, char *device, int promisc,
                        arptype = iface_get_arptype(sock_fd, device, ebuf);
                        if (arptype == -1) 
                                break;
-                       if (map_arphrd_to_dlt(handle, arptype) == -1 ||
+                       map_arphrd_to_dlt(handle, arptype);
+                       if (handle->linktype == -1 ||
                            handle->linktype == DLT_LINUX_SLL ||
                            (handle->linktype == DLT_EN10MB &&
                             (strncmp("isdn", device, 4) == 0 ||
@@ -992,11 +1050,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;
@@ -1337,7 +1395,8 @@ live_open_old(pcap_t *handle, char *device, int promisc,
                 * type that has only an Ethernet packet type as
                 * a link-layer header.
                 */
-               if (map_arphrd_to_dlt(handle, arptype) == -1) {
+               map_arphrd_to_dlt(handle, arptype);
+               if (handle->linktype == -1) {
                        snprintf(ebuf, PCAP_ERRBUF_SIZE,
                                 "interface type of %s not supported", device);
                        break;
@@ -1418,7 +1477,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)
 {
@@ -1542,4 +1601,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