From: guy Date: Sun, 1 May 2005 19:33:11 +0000 (+0000) Subject: Add support for most filter operations for captures with the radiotap X-Git-Tag: libpcap-0.9.1~26 X-Git-Url: https://git.tcpdump.org/libpcap/commitdiff_plain/a27315a887de8300d2c1d6dd380fc2e88038f32f Add support for most filter operations for captures with the radiotap header. Handle MAC broadcasts with the AVS and Prism header as well. --- diff --git a/gencode.c b/gencode.c index af1022ce..74052f79 100644 --- a/gencode.c +++ b/gencode.c @@ -21,7 +21,7 @@ */ #ifndef lint static const char rcsid[] _U_ = - "@(#) $Header: /tcpdump/master/libpcap/gencode.c,v 1.221.2.14 2005-05-01 09:18:08 guy Exp $ (LBL)"; + "@(#) $Header: /tcpdump/master/libpcap/gencode.c,v 1.221.2.15 2005-05-01 19:33:11 guy Exp $ (LBL)"; #endif #ifdef HAVE_CONFIG_H @@ -179,6 +179,7 @@ static struct block *gen_mcmp(enum e_offrel, u_int, u_int, bpf_int32, static struct block *gen_bcmp(enum e_offrel, u_int, u_int, const u_char *); static struct block *gen_ncmp(enum e_offrel, bpf_u_int32, bpf_u_int32, bpf_u_int32, bpf_u_int32, int, bpf_int32); +static struct slist *gen_load_llrel(u_int, u_int); static struct slist *gen_load_a(enum e_offrel, u_int, u_int); static struct slist *gen_loadx_iphdrlen(void); static struct block *gen_uncond(int); @@ -186,6 +187,9 @@ static inline struct block *gen_true(void); static inline struct block *gen_false(void); static struct block *gen_ether_linktype(int); static struct block *gen_linux_sll_linktype(int); +static void insert_radiotap_load_llprefixlen(struct block *); +static void insert_load_llprefixlen(struct block *); +static struct slist *gen_llprefixlen(void); static struct block *gen_linktype(int); static struct block *gen_snap(bpf_u_int32, bpf_u_int32, u_int); static struct block *gen_llc_linktype(int); @@ -474,6 +478,23 @@ finish_parse(p) p->sense = !p->sense; backpatch(p, gen_retblk(0)); root = p->head; + + /* + * Insert before the statements of the first (root) block any + * statements needed to load the lengths of any variable-length + * headers into registers. + * + * XXX - a fancier strategy would be to insert those before the + * statements of all blocks that use those lengths and that + * have no predecessors that use them, so that we only compute + * the lengths if we need them. There might be even better + * approaches than that. However, as we're currently only + * handling variable-length radiotap headers, and as all + * filtering expressions other than raw link[M:N] tests + * require the length of that header, doing more for that + * header length isn't really worth the effort. + */ + insert_load_llprefixlen(root); } void @@ -634,8 +655,15 @@ gen_ncmp(offrel, offset, size, mask, jtype, reverse, v) /* * Various code constructs need to know the layout of the data link - * layer. These variables give the necessary offsets. + * layer. These variables give the necessary offsets from the beginning + * of the packet data. + * + * If the link layer has variable_length headers, the offsets are offsets + * from the end of the link-link-layer header, and "reg_ll_size" is + * the register number for a register containing the length of the + * link-layer header. Otherwise, "reg_ll_size" is -1. */ +static int reg_ll_size; /* * This is the offset of the beginning of the MAC-layer header. @@ -738,6 +766,8 @@ init_linktype(p) orig_linktype = -1; orig_nl = -1; + reg_ll_size = -1; + switch (linktype) { case DLT_ARCNET: @@ -926,21 +956,14 @@ init_linktype(p) * the 802.11 header, containing a bunch of additional * information including radio-level information. * - * XXX - same variable-length header problem, only - * even *more* so; this header is also variable-length, - * with the length being the 16-bit number at an offset - * of 2 from the beginning of the radio header, and it's - * device-dependent (different devices might supply - * different amounts of information), so we can't even - * assume a fixed length for the current version of the - * header. - * - * Therefore, currently, only raw "link[N:M]" filtering is - * supported. + * The radiotap header is variable length, and we + * generate code to compute its length and store it + * in a register. These offsets are relative to the + * beginning of the 802.11 header. */ - off_linktype = -1; - off_nl = -1; - off_nl_nosnap = -1; + off_linktype = 24; + off_nl = 32; /* 802.11+802.2+SNAP */ + off_nl_nosnap = 27; /* 802.11+802.2 */ return; case DLT_ATM_RFC1483: @@ -1096,6 +1119,36 @@ init_linktype(p) /* NOTREACHED */ } +/* + * Load a value relative to the beginning of the link-layer header. + * The link-layer header doesn't necessarily begin at the beginning + * of the packet data; there might be a variable-length prefix containing + * radio information. + */ +static struct slist * +gen_load_llrel(offset, size) + u_int offset, size; +{ + struct slist *s, *s2; + + s = gen_llprefixlen(); + + /* + * If "s" is non-null, it has code to arrange that the X register + * contains the length of the prefix preceding the link-layer + * header. + */ + if (s != NULL) { + s2 = new_stmt(BPF_LD|BPF_IND|size); + s2->s.k = offset; + sappend(s, s2); + } else { + s = new_stmt(BPF_LD|BPF_ABS|size); + s->s.k = offset; + } + return s; +} + /* * Load a value relative to the beginning of the specified header. */ @@ -1109,18 +1162,15 @@ gen_load_a(offrel, offset, size) switch (offrel) { case OR_LINK: - s = new_stmt(BPF_LD|BPF_ABS|size); - s->s.k = offset; + s = gen_load_llrel(offset, size); break; case OR_NET: - s = new_stmt(BPF_LD|BPF_ABS|size); - s->s.k = off_nl + offset; + s = gen_load_llrel(off_nl + offset, size); break; case OR_NET_NOSNAP: - s = new_stmt(BPF_LD|BPF_ABS|size); - s->s.k = off_nl_nosnap + offset; + s = gen_load_llrel(off_nl_nosnap + offset, size); break; case OR_TRAN_IPV4: @@ -1140,8 +1190,7 @@ gen_load_a(offrel, offset, size) break; case OR_TRAN_IPV6: - s = new_stmt(BPF_LD|BPF_ABS|size); - s->s.k = off_nl + 40 + offset; + s = gen_load_llrel(off_nl + 40 + offset, size); break; default: @@ -1152,16 +1201,55 @@ gen_load_a(offrel, offset, size) } /* - * Generate code to load into the X register the length of the IPv4 - * header in the packet. + * Generate code to load into the X register the sum of the length of + * the IPv4 header and any variable-length header preceding the link-layer + * header. */ static struct slist * gen_loadx_iphdrlen() { - struct slist *s; + struct slist *s, *s2; - s = new_stmt(BPF_LDX|BPF_MSH|BPF_B); - s->s.k = off_nl; + s = gen_llprefixlen(); + if (s != NULL) { + /* + * There's a variable-length prefix preceding the + * link-layer header. "s" points to a list of statements + * that put the length of that prefix into the X register. + * The 4*([k]&0xf) addressing mode can't be used, as we + * don't have a constant offset, so we have to load the + * value in question into the A register and add to it + * the value from the X register. + */ + s2 = new_stmt(BPF_LD|BPF_IND|BPF_B); + s2->s.k = off_nl; + sappend(s, s2); + s2 = new_stmt(BPF_ALU|BPF_AND|BPF_K); + s2->s.k = 0xf; + sappend(s, s2); + s2 = new_stmt(BPF_ALU|BPF_LSH|BPF_K); + s2->s.k = 2; + sappend(s, s2); + + /* + * The A register now contains the length of the + * IP header. We need to add to it the length + * of the prefix preceding the link-layer + * header, which is still in the X register, and + * move the result into the X register. + */ + sappend(s, new_stmt(BPF_ALU|BPF_ADD|BPF_X)); + sappend(s, new_stmt(BPF_MISC|BPF_TAX)); + } else { + /* + * There is no variable-length header preceding the + * link-layer header; if there's a fixed-length + * header preceding it, its length is included in + * the off_ variables, so it doesn't need to be added. + */ + s = new_stmt(BPF_LDX|BPF_MSH|BPF_B); + s->s.k = off_nl; + } return s; } @@ -1546,6 +1634,124 @@ gen_linux_sll_linktype(proto) } } +static void +insert_radiotap_load_llprefixlen(b) + struct block *b; +{ + struct slist *s1, *s2; + + /* + * Prepend to the statements in this block code to load the + * length of the radiotap header into the register assigned + * to hold that length, if one has been assigned. + */ + if (reg_ll_size != -1) { + /* + * The 2 bytes at offsets of 2 and 3 from the beginning + * of the radiotap header are the length of the radiotap + * header; unfortunately, it's little-endian, so we have + * to load it a byte at a time and construct the value. + */ + + /* + * Load the high-order byte, at an offset of 3, shift it + * left a byte, and put the result in the X register. + */ + s1 = new_stmt(BPF_LD|BPF_B|BPF_ABS); + s1->s.k = 3; + s2 = new_stmt(BPF_ALU|BPF_LSH|BPF_K); + sappend(s1, s2); + s2->s.k = 8; + s2 = new_stmt(BPF_MISC|BPF_TAX); + sappend(s1, s2); + + /* + * Load the next byte, at an offset of 2, and OR the + * value from the X register into it. + */ + s2 = new_stmt(BPF_LD|BPF_B|BPF_ABS); + sappend(s1, s2); + s2->s.k = 2; + s2 = new_stmt(BPF_ALU|BPF_OR|BPF_X); + sappend(s1, s2); + + /* + * Now allocate a register to hold that value and store + * it. + */ + s2 = new_stmt(BPF_ST); + s2->s.k = reg_ll_size; + sappend(s1, s2); + + /* + * Now move it into the X register. + */ + s2 = new_stmt(BPF_MISC|BPF_TAX); + sappend(s1, s2); + + /* + * Now append all the existing statements in this + * block to these statements. + */ + sappend(s1, b->stmts); + b->stmts = s1; + } +} + + +static void +insert_load_llprefixlen(b) + struct block *b; +{ + switch (linktype) { + + case DLT_IEEE802_11_RADIO: + insert_radiotap_load_llprefixlen(b); + } +} + + +static struct slist * +gen_radiotap_llprefixlen(void) +{ + struct slist *s; + + if (reg_ll_size == -1) { + /* + * We haven't yet assigned a register for the length + * of the radiotap header; allocate one. + */ + reg_ll_size = alloc_reg(); + } + + /* + * Load the register containing the radiotap length + * into the X register. + */ + s = new_stmt(BPF_LDX|BPF_MEM); + s->s.k = reg_ll_size; + return s; +} + +/* + * Generate code to compute the link-layer header length, if necessary, + * putting it into the X register, and to return either a pointer to a + * "struct slist" for the list of statements in that code, or NULL if + * no code is necessary. + */ +static struct slist * +gen_llprefixlen(void) +{ + switch (linktype) { + + case DLT_IEEE802_11_RADIO: + return gen_radiotap_llprefixlen(); + + default: + return NULL; + } +} + /* * Generate code to match a particular packet type by matching the * link-layer type field or fields in the 802.2 LLC header. @@ -1583,9 +1789,9 @@ gen_linktype(proto) case DLT_IEEE802_11: case DLT_PRISM_HEADER: - case DLT_IEEE802_11_RADIO: case DLT_FDDI: case DLT_IEEE802: + case DLT_IEEE802_11_RADIO: case DLT_ATM_RFC1483: case DLT_ATM_CLIP: case DLT_IP_OVER_FC: @@ -3280,6 +3486,15 @@ gen_ipfrag() return b; } +/* + * Generate a comparison to a port value in the transport-layer header + * at the specified offset from the beginning of that header. + * + * XXX - this handles a variable-length prefix preceding the link-layer + * header, such as the radiotap or AVS radio prefix, but doesn't handle + * variable-length link-layer headers (such as Token Ring or 802.11 + * headers). + */ static struct block * gen_portatom(off, v) int off; @@ -3755,6 +3970,19 @@ gen_protochain(v, proto, dir) /*NOTREACHED*/ } + /* + * We don't handle variable-length radiotap here headers yet. + * We might want to add BPF instructions to do the protochain + * work, to simplify that and, on platforms that have a BPF + * interpreter with the new instructions, let the filtering + * be done in the kernel. (We already require a modified BPF + * engine to do the protochain stuff, to support backward + * branches, and backward branch support is unlikely to appear + * in kernel BPF engines.) + */ + if (linktype == DLT_IEEE802_11_RADIO) + bpf_error("'protochain' not supported with radiotap headers"); + no_optimize = 1; /*this code is not compatible with optimzer yet */ /* @@ -4826,6 +5054,13 @@ xfer_to_a(a) return s; } +/* + * Modify "index" to use the value stored into its register as an + * offset relative to the beginning of the header for the protocol + * "proto", and allocate a register and put an item "size" bytes long + * (1, 2, or 4) at that offset into that register, making it the register + * for "index". + */ struct arth * gen_load(proto, index, size) int proto; @@ -4860,6 +5095,9 @@ gen_load(proto, index, size) case Q_LINK: /* + * The offset is relative to the beginning of + * the link-layer header. + * * XXX - what about ATM LANE? Should the index be * relative to the beginning of the AAL5 frame, so * that 0 refers to the beginning of the LE Control @@ -4867,7 +5105,27 @@ gen_load(proto, index, size) * frame, so that 0 refers, for Ethernet LANE, to * the beginning of the destination address? */ - s = xfer_to_x(index); + s = gen_llprefixlen(); + + /* + * If "s" is non-null, it has code to arrange that the + * X register contains the length of the prefix preceding + * the link-layer header. Add to it the offset computed + * into the register specified by "index", and move that + * into the X register. Otherwise, just load into the X + * register the offset computed into the register specifed + * by "index". + */ + if (s != NULL) { + sappend(s, xfer_to_a(index)); + sappend(s, new_stmt(BPF_ALU|BPF_ADD|BPF_X)); + sappend(s, new_stmt(BPF_MISC|BPF_TAX)); + } else + s = xfer_to_x(index); + + /* + * Load the item at the offset we've put in the X register. + */ tmp = new_stmt(BPF_LD|BPF_IND|size); sappend(s, tmp); sappend(index->s, s); @@ -4885,13 +5143,44 @@ gen_load(proto, index, size) #ifdef INET6 case Q_IPV6: #endif - /* XXX Note that we assume a fixed link header here. */ - s = xfer_to_x(index); + /* + * The offset is relative to the beginning of + * the network-layer header. + * XXX - are there any cases where we want + * off_nl_nosnap? + */ + s = gen_llprefixlen(); + + /* + * If "s" is non-null, it has code to arrange that the + * X register contains the length of the prefix preceding + * the link-layer header. Add to it the offset computed + * into the register specified by "index", and move that + * into the X register. Otherwise, just load into the X + * register the offset computed into the register specifed + * by "index". + */ + if (s != NULL) { + sappend(s, xfer_to_a(index)); + sappend(s, new_stmt(BPF_ALU|BPF_ADD|BPF_X)); + sappend(s, new_stmt(BPF_MISC|BPF_TAX)); + } else + s = xfer_to_x(index); + + /* + * Load the item at the sum of the offset we've put in the + * X register and the offset of the start of the network + * layer header. + */ tmp = new_stmt(BPF_LD|BPF_IND|size); tmp->s.k = off_nl; sappend(s, tmp); sappend(index->s, s); + /* + * Do the computation only if the packet contains + * the protocol in question. + */ b = gen_proto_abbrev(proto); if (index->b) gen_and(index->b, b); @@ -4906,7 +5195,29 @@ gen_load(proto, index, size) case Q_IGRP: case Q_PIM: case Q_VRRP: + /* + * The offset is relative to the beginning of + * the transport-layer header. + * XXX - are there any cases where we want + * off_nl_nosnap? + * XXX - we should, if we're built with + * IPv6 support, generate code to load either + * IPv4, IPv6, or both, as appropriate. + */ s = gen_loadx_iphdrlen(); + + /* + * The X register now contains the sum of the offset + * of the beginning of the link-layer header and + * the length of the network-layer header. Load + * into the A register the offset relative to + * the beginning of the transport layer header, + * add the X register to that, move that to the + * X register, and load with an offset from the + * X register equal to the offset of the network + * layer header relative to the beginning of + * the link-layer header. + */ sappend(s, xfer_to_a(index)); sappend(s, new_stmt(BPF_ALU|BPF_ADD|BPF_X)); sappend(s, new_stmt(BPF_MISC|BPF_TAX)); @@ -4914,6 +5225,12 @@ gen_load(proto, index, size) tmp->s.k = off_nl; sappend(index->s, s); + /* + * Do the computation only if the packet contains + * the protocol in question - which is true only + * if this is an IP datagram and is the first or + * only fragment of that datagram. + */ gen_and(gen_proto_abbrev(proto), b = gen_ipfrag()); if (index->b) gen_and(index->b, b); @@ -5139,6 +5456,16 @@ gen_less(n) return b; } +/* + * This is for "byte {idx} {op} {val}"; "idx" is treated as relative to + * the beginning of the link-layer header. + * XXX - that means you can't test values in the radiotap header, but + * as that header is difficult if not impossible to parse generally + * without a loop, that might not be a severe problem. A new keyword + * "radio" could be added for that, although what you'd really want + * would be a way of testing particular radio header values, which + * would generate code appropriate to the radio header in question. + */ struct block * gen_byteop(op, idx, val) int op, idx, val; @@ -5199,7 +5526,10 @@ gen_broadcast(proto) return gen_fhostop(ebroadcast, Q_DST); if (linktype == DLT_IEEE802) return gen_thostop(ebroadcast, Q_DST); - if (linktype == DLT_IEEE802_11) + if (linktype == DLT_IEEE802_11 || + linktype == DLT_IEEE802_11_RADIO_AVS || + linktype == DLT_IEEE802_11_RADIO || + linktype == DLT_PRISM_HEADER) return gen_wlanhostop(ebroadcast, Q_DST); if (linktype == DLT_IP_OVER_FC) return gen_ipfchostop(ebroadcast, Q_DST);