/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright 2012 Milan Jurik. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "snoop_vlan.h" #define IPV4_ONLY 0 #define IPV6_ONLY 1 #define IPV4_AND_IPV6 2 /* * The following constants represent the offsets in bytes from the beginning * of the IP(v6) header of the source and destination IP(v6) addresses. * These are useful when generating filter code. */ #define IPV4_SRCADDR_OFFSET 12 #define IPV4_DSTADDR_OFFSET 16 #define IPV6_SRCADDR_OFFSET 8 #define IPV6_DSTADDR_OFFSET 24 #define IP_VERS(p) (((*(uchar_t *)p) & 0xf0) >> 4) #define MASKED_IPV4_VERS 0x40 #define MASKED_IPV6_VERS 0x60 #define IP_HDR_LEN(p) (((*(uchar_t *)p) & 0xf) * 4) #define TCP_HDR_LEN(p) ((((*((uchar_t *)p+12)) >> 4) & 0xf) * 4) /* * Coding the constant below is tacky, but the compiler won't let us * be more clever. E.g., &((struct ip *)0)->ip_xxx */ #define IP_PROTO_OF(p) (((uchar_t *)p)[9]) /* * AppleTalk uses 802.2 Ethernet encapsulation with LLC/SNAP headers, * for 8 octets of overhead, and the common AppleTalk DDP Ethernet * header is another 4 octets. * * The following constants represents the offsets in bytes from the beginning * of the Ethernet payload to various parts of the DDP header. */ #define AT_DST_NET_OFFSET 12 #define AT_SRC_NET_OFFSET 14 #define AT_DST_NODE_OFFSET 16 #define AT_SRC_NODE_OFFSET 17 /* * Offset for the source and destination zoneid in the ipnet header. */ #define IPNET_SRCZONE_OFFSET 16 #define IPNET_DSTZONE_OFFSET 20 int eaddr; /* need ethernet addr */ int opstack; /* operand stack depth */ /* * These are the operators of the user-level filter. * STOP ends execution of the filter expression and * returns the truth value at the top of the stack. * OP_LOAD_OCTET, OP_LOAD_SHORT and OP_LOAD_LONG pop * an offset value from the stack and load a value of * an appropriate size from the packet (octet, short or * long). The offset is computed from a base value that * may be set via the OP_OFFSET operators. * OP_EQ, OP_NE, OP_GT, OP_GE, OP_LT, OP_LE pop two values * from the stack and return the result of their comparison. * OP_AND, OP_OR, OP_XOR pop two values from the stack and * do perform a bitwise operation on them - returning a result * to the stack. OP_NOT inverts the bits of the value on the * stack. * OP_BRFL and OP_BRTR branch to an offset in the code array * depending on the value at the top of the stack: true (not 0) * or false (0). * OP_ADD, OP_SUB, OP_MUL, OP_DIV and OP_REM pop two values * from the stack and perform arithmetic. * The OP_OFFSET operators change the base from which the * OP_LOAD operators compute their offsets. * OP_OFFSET_ZERO sets the offset to zero - beginning of packet. * OP_OFFSET_LINK sets the base to the first octet after * the link (DLC) header. OP_OFFSET_IP, OP_OFFSET_TCP, * and OP_OFFSET_UDP do the same for those headers - they * set the offset base to the *end* of the header - not the * beginning. The OP_OFFSET_RPC operator is a bit unusual. * It points the base at the cached RPC header. For the * purposes of selection, RPC reply headers look like call * headers except for the direction value. * OP_OFFSET_ETHERTYPE sets base according to the following * algorithm: * if the packet is not VLAN tagged, then set base to * the ethertype field in the ethernet header * else set base to the ethertype field of the VLAN header * OP_OFFSET_POP restores the offset base to the value prior * to the most recent OP_OFFSET call. */ enum optype { OP_STOP = 0, OP_LOAD_OCTET, OP_LOAD_SHORT, OP_LOAD_LONG, OP_LOAD_CONST, OP_LOAD_LENGTH, OP_EQ, OP_NE, OP_GT, OP_GE, OP_LT, OP_LE, OP_AND, OP_OR, OP_XOR, OP_NOT, OP_BRFL, OP_BRTR, OP_ADD, OP_SUB, OP_MUL, OP_DIV, OP_REM, OP_OFFSET_POP, OP_OFFSET_ZERO, OP_OFFSET_LINK, OP_OFFSET_IP, OP_OFFSET_TCP, OP_OFFSET_UDP, OP_OFFSET_RPC, OP_OFFSET_SLP, OP_OFFSET_ETHERTYPE, OP_LAST }; static char *opnames[] = { "STOP", "LOAD_OCTET", "LOAD_SHORT", "LOAD_LONG", "LOAD_CONST", "LOAD_LENGTH", "EQ", "NE", "GT", "GE", "LT", "LE", "AND", "OR", "XOR", "NOT", "BRFL", "BRTR", "ADD", "SUB", "MUL", "DIV", "REM", "OFFSET_POP", "OFFSET_ZERO", "OFFSET_ETHER", "OFFSET_IP", "OFFSET_TCP", "OFFSET_UDP", "OFFSET_RPC", "OP_OFFSET_SLP", "OFFSET_ETHERTYPE", "" }; #define MAXOPS 1024 #define MAXSS 64 static uint_t oplist[MAXOPS]; /* array of operators */ static uint_t *curr_op; /* last op generated */ extern int valid_slp(uchar_t *, int); /* decides if a SLP msg is valid */ extern struct hostent *lgetipnodebyname(const char *, int, int, int *); static void alternation(); static uint_t chain(); static void codeprint(); static void emitop(); static void emitval(); static void expression(); static struct xid_entry *find_rpc(); static void optimize(); static void ethertype_match(); /* * Get a ushort from a possibly unaligned character buffer. * * INPUTS: buffer - where the data is. Must be at least * sizeof(uint16_t) bytes long. * OUPUTS: An unsigned short that contains the data at buffer. * No calls to ntohs or htons are done on the data. */ static uint16_t get_u16(uchar_t *buffer) { uint8_t *bufraw = buffer; /* * ntohs is used only as a cheap way to flip the bits * around on a little endian platform. The value will * still be in host order or network order, depending on * the order it was in when it was passed in. */ return (ntohs(bufraw[0] << 8 | bufraw[1])); } /* * Returns the ULP for an IPv4 or IPv6 packet * Assumes that the packet has already been checked to verify * that it's either IPv4 or IPv6 * * XXX Will need to be updated for AH and ESP * XXX when IPsec is supported for v6. */ static uchar_t ip_proto_of(uchar_t *ip) { uchar_t nxt; boolean_t not_done = B_TRUE; uchar_t *ptr = ip; switch (IP_VERS(ip)) { case IPV4_VERSION: return (IP_PROTO_OF(ip)); case IPV6_VERSION: nxt = ip[6]; ptr += 40; /* size of ip6 header */ do { switch (nxt) { /* * XXX Add IPsec headers here when supported for v6 * XXX (the AH will have a different size...) */ case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_FRAGMENT: case IPPROTO_DSTOPTS: ptr += (8 * (ptr[1] + 1)); nxt = *ptr; break; default: not_done = B_FALSE; break; } } while (not_done); return (nxt); default: break; /* shouldn't get here... */ } return (0); } /* * Returns the total IP header length. * For v4, this includes any options present. * For v6, this is the length of the IPv6 header plus * any extension headers present. * * XXX Will need to be updated for AH and ESP * XXX when IPsec is supported for v6. */ static int ip_hdr_len(uchar_t *ip) { uchar_t nxt; int hdr_len; boolean_t not_done = B_TRUE; int len = 40; /* IPv6 header size */ uchar_t *ptr = ip; switch (IP_VERS(ip)) { case IPV4_VERSION: return (IP_HDR_LEN(ip)); case IPV6_VERSION: nxt = ip[6]; ptr += len; do { switch (nxt) { /* * XXX Add IPsec headers here when supported for v6 * XXX (the AH will have a different size...) */ case IPPROTO_HOPOPTS: case IPPROTO_ROUTING: case IPPROTO_FRAGMENT: case IPPROTO_DSTOPTS: hdr_len = (8 * (ptr[1] + 1)); len += hdr_len; ptr += hdr_len; nxt = *ptr; break; default: not_done = B_FALSE; break; } } while (not_done); return (len); default: break; } return (0); /* not IP */ } static void codeprint() { uint_t *op; printf("User filter:\n"); for (op = oplist; *op; op++) { if (*op <= OP_LAST) printf("\t%2d: %s\n", op - oplist, opnames[*op]); else printf("\t%2d: (%d)\n", op - oplist, *op); switch (*op) { case OP_LOAD_CONST: case OP_BRTR: case OP_BRFL: op++; if ((int)*op < 0) printf("\t%2d: 0x%08x (%d)\n", op - oplist, *op, *op); else printf("\t%2d: %d (0x%08x)\n", op - oplist, *op, *op); } } printf("\t%2d: STOP\n", op - oplist); printf("\n"); } /* * Take a pass through the generated code and optimize * branches. A branch true (BRTR) that has another BRTR * at its destination can use the address of the destination * BRTR. A BRTR that points to a BRFL (branch false) should * point to the address following the BRFL. * A similar optimization applies to BRFL operators. */ static void optimize(uint_t *oplistp) { uint_t *op; for (op = oplistp; *op; op++) { switch (*op) { case OP_LOAD_CONST: op++; break; case OP_BRTR: op++; optimize(&oplist[*op]); if (oplist[*op] == OP_BRFL) *op += 2; else if (oplist[*op] == OP_BRTR) *op = oplist[*op + 1]; break; case OP_BRFL: op++; optimize(&oplist[*op]); if (oplist[*op] == OP_BRTR) *op += 2; else if (oplist[*op] == OP_BRFL) *op = oplist[*op + 1]; break; } } } /* * RPC packets are tough to filter. * While the call packet has all the interesting * info: program number, version, procedure etc, * the reply packet has none of this information. * If we want to do useful filtering based on this * information then we have to stash the information * from the call packet, and use the XID in the reply * to find the stashed info. The stashed info is * kept in a circular lifo, assuming that a call packet * will be followed quickly by its reply. */ struct xid_entry { unsigned x_xid; /* The XID (32 bits) */ unsigned x_dir; /* CALL or REPLY */ unsigned x_rpcvers; /* Protocol version (2) */ unsigned x_prog; /* RPC program number */ unsigned x_vers; /* RPC version number */ unsigned x_proc; /* RPC procedure number */ }; static struct xid_entry xe_table[XID_CACHE_SIZE]; static struct xid_entry *xe_first = &xe_table[0]; static struct xid_entry *xe = &xe_table[0]; static struct xid_entry *xe_last = &xe_table[XID_CACHE_SIZE - 1]; static struct xid_entry * find_rpc(struct rpc_msg *rpc) { struct xid_entry *x; for (x = xe; x >= xe_first; x--) if (x->x_xid == rpc->rm_xid) return (x); for (x = xe_last; x > xe; x--) if (x->x_xid == rpc->rm_xid) return (x); return (NULL); } static void stash_rpc(struct rpc_msg *rpc) { struct xid_entry *x; if (find_rpc(rpc)) return; x = xe++; if (xe > xe_last) xe = xe_first; x->x_xid = rpc->rm_xid; x->x_dir = htonl(REPLY); x->x_prog = rpc->rm_call.cb_prog; x->x_vers = rpc->rm_call.cb_vers; x->x_proc = rpc->rm_call.cb_proc; } /* * SLP can multicast requests, and recieve unicast replies in which * neither the source nor destination port is identifiable as a SLP * port. Hence, we need to do as RPC does, and keep track of packets we * are interested in. For SLP, however, we use ports, not XIDs, and * a smaller cache size is more efficient since every incoming packet * needs to be checked. */ #define SLP_CACHE_SIZE 64 static uint_t slp_table[SLP_CACHE_SIZE]; static int slp_index = 0; /* * Returns the index of dport in the table if found, otherwise -1. */ static int find_slp(uint_t dport) { int i; if (!dport) return (0); for (i = slp_index; i >= 0; i--) if (slp_table[i] == dport) { return (i); } for (i = SLP_CACHE_SIZE - 1; i > slp_index; i--) if (slp_table[i] == dport) { return (i); } return (-1); } static void stash_slp(uint_t sport) { if (slp_table[slp_index - 1] == sport) /* avoid redundancy due to multicast retransmissions */ return; slp_table[slp_index++] = sport; if (slp_index == SLP_CACHE_SIZE) slp_index = 0; } /* * This routine takes a packet and returns true or false * according to whether the filter expression selects it * or not. * We assume here that offsets for short and long values * are even - we may die with an alignment error if the * CPU doesn't support odd addresses. Note that long * values are loaded as two shorts so that 32 bit word * alignment isn't important. * * IPv6 is a bit stickier to handle than IPv4... */ int want_packet(uchar_t *pkt, int len, int origlen) { uint_t stack[MAXSS]; /* operand stack */ uint_t *op; /* current operator */ uint_t *sp; /* top of operand stack */ uchar_t *base; /* base for offsets into packet */ uchar_t *ip; /* addr of IP header, unaligned */ uchar_t *tcp; /* addr of TCP header, unaligned */ uchar_t *udp; /* addr of UDP header, unaligned */ struct rpc_msg rpcmsg; /* addr of RPC header */ struct rpc_msg *rpc; int newrpc = 0; uchar_t *slphdr; /* beginning of SLP header */ uint_t slp_sport, slp_dport; int off, header_size; uchar_t *offstack[MAXSS]; /* offset stack */ uchar_t **offp; /* current offset */ uchar_t *opkt = NULL; uint_t olen; sp = stack; *sp = 1; base = pkt; offp = offstack; header_size = (*interface->header_len)((char *)pkt, len); for (op = oplist; *op; op++) { switch ((enum optype) *op) { case OP_LOAD_OCTET: if ((base + *sp) > (pkt + len)) return (0); /* packet too short */ *sp = *((uchar_t *)(base + *sp)); break; case OP_LOAD_SHORT: off = *sp; if ((base + off + sizeof (uint16_t) - 1) > (pkt + len)) return (0); /* packet too short */ *sp = ntohs(get_u16((uchar_t *)(base + off))); break; case OP_LOAD_LONG: off = *sp; if ((base + off + sizeof (uint32_t) - 1) > (pkt + len)) return (0); /* packet too short */ /* * Handle 3 possible alignments */ switch ((((unsigned)base) + off) % sizeof (uint_t)) { case 0: *sp = *(uint_t *)(base + off); break; case 2: *((ushort_t *)(sp)) = *((ushort_t *)(base + off)); *(((ushort_t *)sp) + 1) = *((ushort_t *)(base + off) + 1); break; case 1: case 3: *((uchar_t *)(sp)) = *((uchar_t *)(base + off)); *(((uchar_t *)sp) + 1) = *((uchar_t *)(base + off) + 1); *(((uchar_t *)sp) + 2) = *((uchar_t *)(base + off) + 2); *(((uchar_t *)sp) + 3) = *((uchar_t *)(base + off) + 3); break; } *sp = ntohl(*sp); break; case OP_LOAD_CONST: if (sp >= &stack[MAXSS]) return (0); *(++sp) = *(++op); break; case OP_LOAD_LENGTH: if (sp >= &stack[MAXSS]) return (0); *(++sp) = origlen; break; case OP_EQ: if (sp < &stack[1]) return (0); sp--; *sp = *sp == *(sp + 1); break; case OP_NE: if (sp < &stack[1]) return (0); sp--; *sp = *sp != *(sp + 1); break; case OP_GT: if (sp < &stack[1]) return (0); sp--; *sp = *sp > *(sp + 1); break; case OP_GE: if (sp < &stack[1]) return (0); sp--; *sp = *sp >= *(sp + 1); break; case OP_LT: if (sp < &stack[1]) return (0); sp--; *sp = *sp < *(sp + 1); break; case OP_LE: if (sp < &stack[1]) return (0); sp--; *sp = *sp <= *(sp + 1); break; case OP_AND: if (sp < &stack[1]) return (0); sp--; *sp &= *(sp + 1); break; case OP_OR: if (sp < &stack[1]) return (0); sp--; *sp |= *(sp + 1); break; case OP_XOR: if (sp < &stack[1]) return (0); sp--; *sp ^= *(sp + 1); break; case OP_NOT: *sp = !*sp; break; case OP_BRFL: op++; if (!*sp) op = &oplist[*op] - 1; break; case OP_BRTR: op++; if (*sp) op = &oplist[*op] - 1; break; case OP_ADD: if (sp < &stack[1]) return (0); sp--; *sp += *(sp + 1); break; case OP_SUB: if (sp < &stack[1]) return (0); sp--; *sp -= *(sp + 1); break; case OP_MUL: if (sp < &stack[1]) return (0); sp--; *sp *= *(sp + 1); break; case OP_DIV: if (sp < &stack[1]) return (0); sp--; *sp /= *(sp + 1); break; case OP_REM: if (sp < &stack[1]) return (0); sp--; *sp %= *(sp + 1); break; case OP_OFFSET_POP: if (offp < &offstack[0]) return (0); base = *offp--; if (opkt != NULL) { pkt = opkt; len = olen; opkt = NULL; } break; case OP_OFFSET_ZERO: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; base = pkt; break; case OP_OFFSET_LINK: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; base = pkt + header_size; /* * If the offset exceeds the packet length, * we should not be interested in this packet... * Just return 0. */ if (base > pkt + len) { return (0); } break; case OP_OFFSET_IP: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; ip = pkt + header_size; base = ip + ip_hdr_len(ip); if (base == ip) { return (0); /* not IP */ } if (base > pkt + len) { return (0); /* bad pkt */ } break; case OP_OFFSET_TCP: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; ip = pkt + header_size; tcp = ip + ip_hdr_len(ip); if (tcp == ip) { return (0); /* not IP */ } base = tcp + TCP_HDR_LEN(tcp); if (base > pkt + len) { return (0); } break; case OP_OFFSET_UDP: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; ip = pkt + header_size; udp = ip + ip_hdr_len(ip); if (udp == ip) { return (0); /* not IP */ } base = udp + sizeof (struct udphdr); if (base > pkt + len) { return (0); } break; case OP_OFFSET_RPC: if (offp >= &offstack[MAXSS]) return (0); *++offp = base; ip = pkt + header_size; rpc = NULL; if (IP_VERS(ip) != IPV4_VERSION && IP_VERS(ip) != IPV6_VERSION) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; } switch (ip_proto_of(ip)) { case IPPROTO_UDP: udp = ip + ip_hdr_len(ip); rpc = (struct rpc_msg *)(udp + sizeof (struct udphdr)); break; case IPPROTO_TCP: tcp = ip + ip_hdr_len(ip); /* * Need to skip an extra 4 for the xdr_rec * field. */ rpc = (struct rpc_msg *)(tcp + TCP_HDR_LEN(tcp) + 4); break; } /* * We need to have at least 24 bytes of a RPC * packet to look at to determine the validity * of it. */ if (rpc == NULL || (uchar_t *)rpc + 24 > pkt + len) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; } /* align */ (void) memcpy(&rpcmsg, rpc, 24); if (!valid_rpc((char *)&rpcmsg, 24)) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; } if (ntohl(rpcmsg.rm_direction) == CALL) { base = (uchar_t *)rpc; newrpc = 1; if (sp >= &stack[MAXSS]) return (0); *(++sp) = 1; } else { opkt = pkt; olen = len; pkt = base = (uchar_t *)find_rpc(&rpcmsg); len = sizeof (struct xid_entry); if (sp >= &stack[MAXSS]) return (0); *(++sp) = base != NULL; } break; case OP_OFFSET_SLP: slphdr = NULL; ip = pkt + header_size; if (IP_VERS(ip) != IPV4_VERSION && IP_VERS(ip) != IPV6_VERSION) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; } switch (ip_proto_of(ip)) { struct udphdr udp_h; struct tcphdr tcp_h; case IPPROTO_UDP: udp = ip + ip_hdr_len(ip); /* align */ memcpy(&udp_h, udp, sizeof (udp_h)); slp_sport = ntohs(udp_h.uh_sport); slp_dport = ntohs(udp_h.uh_dport); slphdr = udp + sizeof (struct udphdr); break; case IPPROTO_TCP: tcp = ip + ip_hdr_len(ip); /* align */ memcpy(&tcp_h, tcp, sizeof (tcp_h)); slp_sport = ntohs(tcp_h.th_sport); slp_dport = ntohs(tcp_h.th_dport); slphdr = tcp + TCP_HDR_LEN(tcp); break; } if (slphdr == NULL || slphdr > pkt + len) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; } if (slp_sport == 427 || slp_dport == 427) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 1; if (slp_sport != 427 && slp_dport == 427) stash_slp(slp_sport); break; } else if (find_slp(slp_dport) != -1) { if (valid_slp(slphdr, len)) { if (sp >= &stack[MAXSS]) return (0); *(++sp) = 1; break; } /* else fallthrough to reject */ } if (sp >= &stack[MAXSS]) return (0); *(++sp) = 0; break; case OP_OFFSET_ETHERTYPE: /* * Set base to the location of the ethertype as * appropriate for this link type. Note that it's * not called "ethertype" for every link type, but * we need to call it something. */ if (offp >= &offstack[MAXSS]) return (0); *++offp = base; base = pkt + interface->network_type_offset; /* * Below, we adjust the offset for unusual * link-layer headers that may have the protocol * type in a variable location beyond what was set * above. */ switch (interface->mac_type) { case DL_ETHER: case DL_CSMACD: /* * If this is a VLAN-tagged packet, we need * to point to the ethertype field in the * VLAN header. Move past the ethertype * field in the ethernet header. */ if (ntohs(get_u16(base)) == ETHERTYPE_VLAN) base += (ENCAP_ETHERTYPE_OFF); break; } if (base > pkt + len) { /* Went too far, drop the packet */ return (0); } break; } } if (*sp && newrpc) stash_rpc(&rpcmsg); return (*sp); } static void load_const(uint_t constval) { emitop(OP_LOAD_CONST); emitval(constval); } static void load_value(int offset, int len) { if (offset >= 0) load_const(offset); switch (len) { case 1: emitop(OP_LOAD_OCTET); break; case 2: emitop(OP_LOAD_SHORT); break; case 4: emitop(OP_LOAD_LONG); break; } } /* * Emit code to compare a field in * the packet against a constant value. */ static void compare_value(uint_t offset, uint_t len, uint_t val) { load_const(val); load_value(offset, len); emitop(OP_EQ); } static void compare_addr_v4(uint_t offset, uint_t len, uint_t val) { load_const(ntohl(val)); load_value(offset, len); emitop(OP_EQ); } static void compare_addr_v6(uint_t offset, uint_t len, struct in6_addr val) { int i; uint32_t value; for (i = 0; i < len; i += 4) { value = ntohl(*(uint32_t *)&val.s6_addr[i]); load_const(value); load_value(offset + i, 4); emitop(OP_EQ); if (i != 0) emitop(OP_AND); } } /* * Same as above except do the comparison * after and'ing a mask value. Useful * for comparing IP network numbers */ static void compare_value_mask(uint_t offset, uint_t len, uint_t val, int mask) { load_value(offset, len); load_const(mask); emitop(OP_AND); load_const(val); emitop(OP_EQ); } /* * Compare two zoneid's. The arg val passed in is stored in network * byte order. */ static void compare_value_zone(uint_t offset, uint32_t val) { load_const(ntohl(val)); load_value(offset, 4); emitop(OP_EQ); } /* Emit an operator into the code array */ static void emitop(enum optype opcode) { if (curr_op >= &oplist[MAXOPS]) pr_err("expression too long"); *curr_op++ = opcode; } /* * Remove n operators recently emitted into * the code array. Used by alternation(). */ static void unemit(int numops) { curr_op -= numops; } /* * Same as emitop except that we're emitting * a value that's not an operator. */ static void emitval(uint_t val) { if (curr_op >= &oplist[MAXOPS]) pr_err("expression too long"); *curr_op++ = val; } /* * Used to chain forward branches together * for later resolution by resolve_chain(). */ static uint_t chain(int p) { uint_t pos = curr_op - oplist; emitval(p); return (pos); } /* * Proceed backward through the code array * following a chain of forward references. * At each reference install the destination * branch offset. */ static void resolve_chain(uint_t p) { uint_t n; uint_t pos = curr_op - oplist; while (p) { n = oplist[p]; oplist[p] = pos; p = n; } } #define EQ(val) (strcmp(token, val) == 0) char *tkp, *sav_tkp; char *token; enum tokentype tokentype; uint_t tokenval; /* * This is the scanner. Each call returns the next * token in the filter expression. A token is either: * EOL: The end of the line - no more tokens. * ALPHA: A name that begins with a letter and contains * letters or digits, hyphens or underscores. * NUMBER: A number. The value can be represented as * a decimal value (1234) or an octal value * that begins with zero (066) or a hex value * that begins with 0x or 0X (0xff). * FIELD: A name followed by a left square bracket. * ADDR_IP: An IP address. Any sequence of digits * separated by dots e.g. 109.104.40.13 * ADDR_ETHER: An ethernet address. Any sequence of hex * digits separated by colons e.g. 8:0:20:0:76:39 * SPECIAL: A special character e.g. ">" or "(". The scanner * correctly handles digraphs - two special characters * that constitute a single token e.g. "==" or ">=". * ADDR_IP6: An IPv6 address. * * ADDR_AT: An AppleTalk Phase II address. A sequence of two numbers * separated by a dot. * * The current token is maintained in "token" and and its * type in "tokentype". If tokentype is NUMBER then the * value is held in "tokenval". */ static const char *namechars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-."; static const char *numchars = "0123456789abcdefABCDEFXx:."; void next() { static int savechar; char *p; int size, size1; int base, colons, dots, alphas, double_colon; colons = 0; double_colon = 0; if (*tkp == '\0') { token = tkp; *tkp = savechar; } sav_tkp = tkp; while (isspace(*tkp)) tkp++; token = tkp; if (*token == '\0') { tokentype = EOL; return; } /* A token containing ':' cannot be ALPHA type */ tkp = token + strspn(token, numchars); for (p = token; p < tkp; p++) { if (*p == ':') { colons++; if (*(p+1) == ':') double_colon++; } } tkp = token; if (isalpha(*tkp) && !colons) { tokentype = ALPHA; tkp += strspn(tkp, namechars); if (*tkp == '[') { tokentype = FIELD; *tkp++ = '\0'; } } else /* * RFC1123 states that host names may now start with digits. Need * to change parser to account for this. Also, need to distinguish * between 1.2.3.4 and 1.2.3.a where the first case is an IP address * and the second is a domain name. 333aaa needs to be distinguished * from 0x333aaa. The first is a host name and the second is a number. * * The (colons > 1) conditional differentiates between ethernet * and IPv6 addresses, and an expression of the form base[expr:size], * which can only contain one ':' character. */ if (isdigit(*tkp) || colons > 1) { tkp = token + strspn(token, numchars); dots = alphas = 0; for (p = token; p < tkp; p++) { if (*p == '.') dots++; else if (isalpha(*p)) alphas = 1; } if (colons > 1) { if (colons == 5 && double_colon == 0) { tokentype = ADDR_ETHER; } else { tokentype = ADDR_IP6; } } else if (dots) { size = tkp - token; size1 = strspn(token, "0123456789."); if (dots == 1 && size == size1) { tokentype = ADDR_AT; } else if (dots != 3 || size != size1) { tokentype = ALPHA; if (*tkp != '\0' && !isspace(*tkp)) { tkp += strspn(tkp, namechars); if (*tkp == '[') { tokentype = FIELD; *tkp++ = '\0'; } } } else tokentype = ADDR_IP; } else if (token + strspn(token, namechars) <= tkp) { /* * With the above check, if there are more * characters after the last digit, assume * that it is not a number. */ tokentype = NUMBER; p = tkp; tkp = token; base = 10; if (*tkp == '0') { base = 8; tkp++; if (*tkp == 'x' || *tkp == 'X') base = 16; } if ((base == 10 || base == 8) && alphas) { tokentype = ALPHA; tkp = p; } else if (base == 16) { size = 2 + strspn(token+2, "0123456789abcdefABCDEF"); size1 = p - token; if (size != size1) { tokentype = ALPHA; tkp = p; } else /* * handles the case of 0x so an error message * is not printed. Treats 0x as 0. */ if (size == 2) { tokenval = 0; tkp = token +2; } else { tokenval = strtoul(token, &tkp, base); } } else { tokenval = strtoul(token, &tkp, base); } } else { tokentype = ALPHA; tkp += strspn(tkp, namechars); if (*tkp == '[') { tokentype = FIELD; *tkp++ = '\0'; } } } else { tokentype = SPECIAL; tkp++; if ((*token == '=' && *tkp == '=') || (*token == '>' && *tkp == '=') || (*token == '<' && *tkp == '=') || (*token == '!' && *tkp == '=')) tkp++; } savechar = *tkp; *tkp = '\0'; } typedef struct match_type { char *m_name; int m_offset; int m_size; int m_value; int m_depend; enum optype m_optype; } match_type_t; static match_type_t ether_match_types[] = { /* * Table initialized assuming Ethernet data link headers. * m_offset is an offset beyond the offset op, which is why * the offset is zero for when snoop needs to check an ethertype. */ "ip", 0, 2, ETHERTYPE_IP, -1, OP_OFFSET_ETHERTYPE, "ip6", 0, 2, ETHERTYPE_IPV6, -1, OP_OFFSET_ETHERTYPE, "arp", 0, 2, ETHERTYPE_ARP, -1, OP_OFFSET_ETHERTYPE, "rarp", 0, 2, ETHERTYPE_REVARP, -1, OP_OFFSET_ETHERTYPE, "pppoed", 0, 2, ETHERTYPE_PPPOED, -1, OP_OFFSET_ETHERTYPE, "pppoes", 0, 2, ETHERTYPE_PPPOES, -1, OP_OFFSET_ETHERTYPE, "tcp", 9, 1, IPPROTO_TCP, 0, OP_OFFSET_LINK, "tcp", 6, 1, IPPROTO_TCP, 1, OP_OFFSET_LINK, "udp", 9, 1, IPPROTO_UDP, 0, OP_OFFSET_LINK, "udp", 6, 1, IPPROTO_UDP, 1, OP_OFFSET_LINK, "icmp", 9, 1, IPPROTO_ICMP, 0, OP_OFFSET_LINK, "icmp6", 6, 1, IPPROTO_ICMPV6, 1, OP_OFFSET_LINK, "ospf", 9, 1, IPPROTO_OSPF, 0, OP_OFFSET_LINK, "ospf", 6, 1, IPPROTO_OSPF, 1, OP_OFFSET_LINK, "ip-in-ip", 9, 1, IPPROTO_ENCAP, 0, OP_OFFSET_LINK, "esp", 9, 1, IPPROTO_ESP, 0, OP_OFFSET_LINK, "esp", 6, 1, IPPROTO_ESP, 1, OP_OFFSET_LINK, "ah", 9, 1, IPPROTO_AH, 0, OP_OFFSET_LINK, "ah", 6, 1, IPPROTO_AH, 1, OP_OFFSET_LINK, "sctp", 9, 1, IPPROTO_SCTP, 0, OP_OFFSET_LINK, "sctp", 6, 1, IPPROTO_SCTP, 1, OP_OFFSET_LINK, 0, 0, 0, 0, 0, 0 }; static match_type_t ipnet_match_types[] = { /* * Table initialized assuming Ethernet data link headers. * m_offset is an offset beyond the offset op, which is why * the offset is zero for when snoop needs to check an ethertype. */ "ip", 0, 1, IPV4_VERSION, -1, OP_OFFSET_ETHERTYPE, "ip6", 0, 1, IPV6_VERSION, -1, OP_OFFSET_ETHERTYPE, "tcp", 9, 1, IPPROTO_TCP, 0, OP_OFFSET_LINK, "tcp", 6, 1, IPPROTO_TCP, 1, OP_OFFSET_LINK, "udp", 9, 1, IPPROTO_UDP, 0, OP_OFFSET_LINK, "udp", 6, 1, IPPROTO_UDP, 1, OP_OFFSET_LINK, "icmp", 9, 1, IPPROTO_ICMP, 0, OP_OFFSET_LINK, "icmp6", 6, 1, IPPROTO_ICMPV6, 1, OP_OFFSET_LINK, "ospf", 9, 1, IPPROTO_OSPF, 0, OP_OFFSET_LINK, "ospf", 6, 1, IPPROTO_OSPF, 1, OP_OFFSET_LINK, "ip-in-ip", 9, 1, IPPROTO_ENCAP, 0, OP_OFFSET_LINK, "esp", 9, 1, IPPROTO_ESP, 0, OP_OFFSET_LINK, "esp", 6, 1, IPPROTO_ESP, 1, OP_OFFSET_LINK, "ah", 9, 1, IPPROTO_AH, 0, OP_OFFSET_LINK, "ah", 6, 1, IPPROTO_AH, 1, OP_OFFSET_LINK, "sctp", 9, 1, IPPROTO_SCTP, 0, OP_OFFSET_LINK, "sctp", 6, 1, IPPROTO_SCTP, 1, OP_OFFSET_LINK, 0, 0, 0, 0, 0, 0 }; static match_type_t iptun_match_types[] = { "ip", 0, 1, IPPROTO_ENCAP, -1, OP_OFFSET_ETHERTYPE, "ip6", 0, 1, IPPROTO_IPV6, -1, OP_OFFSET_ETHERTYPE, "tcp", 9, 1, IPPROTO_TCP, 0, OP_OFFSET_LINK, "tcp", 6, 1, IPPROTO_TCP, 1, OP_OFFSET_LINK, "udp", 9, 1, IPPROTO_UDP, 0, OP_OFFSET_LINK, "udp", 6, 1, IPPROTO_UDP, 1, OP_OFFSET_LINK, "icmp", 9, 1, IPPROTO_ICMP, 0, OP_OFFSET_LINK, "icmp6", 6, 1, IPPROTO_ICMPV6, 1, OP_OFFSET_LINK, "ospf", 9, 1, IPPROTO_OSPF, 0, OP_OFFSET_LINK, "ospf", 6, 1, IPPROTO_OSPF, 1, OP_OFFSET_LINK, "ip-in-ip", 9, 1, IPPROTO_ENCAP, 0, OP_OFFSET_LINK, "esp", 9, 1, IPPROTO_ESP, 0, OP_OFFSET_LINK, "esp", 6, 1, IPPROTO_ESP, 1, OP_OFFSET_LINK, "ah", 9, 1, IPPROTO_AH, 0, OP_OFFSET_LINK, "ah", 6, 1, IPPROTO_AH, 1, OP_OFFSET_LINK, "sctp", 9, 1, IPPROTO_SCTP, 0, OP_OFFSET_LINK, "sctp", 6, 1, IPPROTO_SCTP, 1, OP_OFFSET_LINK, 0, 0, 0, 0, 0, 0 }; static void generate_check(match_type_t match_types[], int index, int type) { match_type_t *mtp = &match_types[index]; /* * Note: this code assumes the above dependencies are * not cyclic. This *should* always be true. */ if (mtp->m_depend != -1) generate_check(match_types, mtp->m_depend, type); emitop(mtp->m_optype); load_value(mtp->m_offset, mtp->m_size); load_const(mtp->m_value); emitop(OP_OFFSET_POP); emitop(OP_EQ); if (mtp->m_depend != -1) emitop(OP_AND); } /* * Generate code based on the keyword argument. * This word is looked up in the match_types table * and checks a field within the packet for a given * value e.g. ether or ip type field. The match * can also have a dependency on another entry e.g. * "tcp" requires that the packet also be "ip". */ static int comparison(char *s) { unsigned int i, n_checks = 0; match_type_t *match_types; switch (interface->mac_type) { case DL_ETHER: match_types = ether_match_types; break; case DL_IPNET: match_types = ipnet_match_types; break; case DL_IPV4: case DL_IPV6: case DL_6TO4: match_types = iptun_match_types; break; default: return (0); } for (i = 0; match_types[i].m_name != NULL; i++) { if (strcmp(s, match_types[i].m_name) != 0) continue; n_checks++; generate_check(match_types, i, interface->mac_type); if (n_checks > 1) emitop(OP_OR); } return (n_checks > 0); } enum direction dir; /* * Generate code to match an IP address. The address * may be supplied either as a hostname or in dotted format. * For source packets both the IP source address and ARP * src are checked. * Note: we don't check packet type here - whether IP or ARP. * It's possible that we'll do an improper match. */ static void ipaddr_match(enum direction which, char *hostname, int inet_type) { bool_t found_host; int m = 0, n = 0; uint_t *addr4ptr; uint_t addr4; struct in6_addr *addr6ptr; int h_addr_index; struct hostent *hp = NULL; int error_num = 0; boolean_t freehp = B_FALSE; boolean_t first = B_TRUE; /* * The addr4offset and addr6offset variables simplify the code which * generates the address comparison filter. With these two variables, * duplicate code need not exist for the TO and FROM case. * A value of -1 describes the ANY case (TO and FROM). */ int addr4offset; int addr6offset; found_host = 0; if (tokentype == ADDR_IP) { hp = lgetipnodebyname(hostname, AF_INET, 0, &error_num); if (hp == NULL) { hp = getipnodebyname(hostname, AF_INET, 0, &error_num); freehp = 1; } if (hp == NULL) { if (error_num == TRY_AGAIN) { pr_err("couldn't resolve %s (try again later)", hostname); } else { pr_err("couldn't resolve %s", hostname); } } inet_type = IPV4_ONLY; } else if (tokentype == ADDR_IP6) { hp = lgetipnodebyname(hostname, AF_INET6, 0, &error_num); if (hp == NULL) { hp = getipnodebyname(hostname, AF_INET6, 0, &error_num); freehp = 1; } if (hp == NULL) { if (error_num == TRY_AGAIN) { pr_err("couldn't resolve %s (try again later)", hostname); } else { pr_err("couldn't resolve %s", hostname); } } inet_type = IPV6_ONLY; } else { /* Some hostname i.e. tokentype is ALPHA */ switch (inet_type) { case IPV4_ONLY: /* Only IPv4 address is needed */ hp = lgetipnodebyname(hostname, AF_INET, 0, &error_num); if (hp == NULL) { hp = getipnodebyname(hostname, AF_INET, 0, &error_num); freehp = 1; } if (hp != NULL) { found_host = 1; } break; case IPV6_ONLY: /* Only IPv6 address is needed */ hp = lgetipnodebyname(hostname, AF_INET6, 0, &error_num); if (hp == NULL) { hp = getipnodebyname(hostname, AF_INET6, 0, &error_num); freehp = 1; } if (hp != NULL) { found_host = 1; } break; case IPV4_AND_IPV6: /* Both IPv4 and IPv6 are needed */ hp = lgetipnodebyname(hostname, AF_INET6, AI_ALL | AI_V4MAPPED, &error_num); if (hp == NULL) { hp = getipnodebyname(hostname, AF_INET6, AI_ALL | AI_V4MAPPED, &error_num); freehp = 1; } if (hp != NULL) { found_host = 1; } break; default: found_host = 0; } if (!found_host) { if (error_num == TRY_AGAIN) { pr_err("could not resolve %s (try again later)", hostname); } else { pr_err("could not resolve %s", hostname); } } } if (hp == NULL) return; switch (which) { case TO: addr4offset = IPV4_DSTADDR_OFFSET; addr6offset = IPV6_DSTADDR_OFFSET; break; case FROM: addr4offset = IPV4_SRCADDR_OFFSET; addr6offset = IPV6_SRCADDR_OFFSET; break; case ANY: addr4offset = -1; addr6offset = -1; break; } /* * The code below generates the filter. */ if (hp->h_addrtype == AF_INET) { ethertype_match(interface->network_type_ip); emitop(OP_BRFL); n = chain(n); emitop(OP_OFFSET_LINK); h_addr_index = 0; addr4ptr = (uint_t *)hp->h_addr_list[h_addr_index]; while (addr4ptr != NULL) { if (addr4offset == -1) { compare_addr_v4(IPV4_SRCADDR_OFFSET, 4, *addr4ptr); emitop(OP_BRTR); m = chain(m); compare_addr_v4(IPV4_DSTADDR_OFFSET, 4, *addr4ptr); } else { compare_addr_v4(addr4offset, 4, *addr4ptr); } addr4ptr = (uint_t *)hp->h_addr_list[++h_addr_index]; if (addr4ptr != NULL) { emitop(OP_BRTR); m = chain(m); } } if (m != 0) { resolve_chain(m); } emitop(OP_OFFSET_POP); resolve_chain(n); } else { /* first pass: IPv4 addresses */ h_addr_index = 0; addr6ptr = (struct in6_addr *)hp->h_addr_list[h_addr_index]; first = B_TRUE; while (addr6ptr != NULL) { if (IN6_IS_ADDR_V4MAPPED(addr6ptr)) { if (first) { ethertype_match( interface->network_type_ip); emitop(OP_BRFL); n = chain(n); emitop(OP_OFFSET_LINK); first = B_FALSE; } else { emitop(OP_BRTR); m = chain(m); } IN6_V4MAPPED_TO_INADDR(addr6ptr, (struct in_addr *)&addr4); if (addr4offset == -1) { compare_addr_v4(IPV4_SRCADDR_OFFSET, 4, addr4); emitop(OP_BRTR); m = chain(m); compare_addr_v4(IPV4_DSTADDR_OFFSET, 4, addr4); } else { compare_addr_v4(addr4offset, 4, addr4); } } addr6ptr = (struct in6_addr *) hp->h_addr_list[++h_addr_index]; } /* second pass: IPv6 addresses */ h_addr_index = 0; addr6ptr = (struct in6_addr *)hp->h_addr_list[h_addr_index]; first = B_TRUE; while (addr6ptr != NULL) { if (!IN6_IS_ADDR_V4MAPPED(addr6ptr)) { if (first) { /* * bypass check for IPv6 addresses * when we have an IPv4 packet */ if (n != 0) { emitop(OP_BRTR); m = chain(m); emitop(OP_BRFL); m = chain(m); resolve_chain(n); n = 0; } ethertype_match( interface->network_type_ipv6); emitop(OP_BRFL); n = chain(n); emitop(OP_OFFSET_LINK); first = B_FALSE; } else { emitop(OP_BRTR); m = chain(m); } if (addr6offset == -1) { compare_addr_v6(IPV6_SRCADDR_OFFSET, 16, *addr6ptr); emitop(OP_BRTR); m = chain(m); compare_addr_v6(IPV6_DSTADDR_OFFSET, 16, *addr6ptr); } else { compare_addr_v6(addr6offset, 16, *addr6ptr); } } addr6ptr = (struct in6_addr *) hp->h_addr_list[++h_addr_index]; } if (m != 0) { resolve_chain(m); } emitop(OP_OFFSET_POP); resolve_chain(n); } /* only free struct hostent returned by getipnodebyname() */ if (freehp) { freehostent(hp); } } /* * Match on zoneid. The arg zone passed in is in network byte order. */ static void zone_match(enum direction which, uint32_t zone) { switch (which) { case TO: compare_value_zone(IPNET_DSTZONE_OFFSET, zone); break; case FROM: compare_value_zone(IPNET_SRCZONE_OFFSET, zone); break; case ANY: compare_value_zone(IPNET_SRCZONE_OFFSET, zone); compare_value_zone(IPNET_DSTZONE_OFFSET, zone); emitop(OP_OR); } } /* * Generate code to match an AppleTalk address. The address * must be given as two numbers with a dot between * */ static void ataddr_match(enum direction which, char *hostname) { uint_t net; uint_t node; uint_t m, n; sscanf(hostname, "%u.%u", &net, &node); emitop(OP_OFFSET_LINK); switch (which) { case TO: compare_value(AT_DST_NET_OFFSET, 2, net); emitop(OP_BRFL); m = chain(0); compare_value(AT_DST_NODE_OFFSET, 1, node); resolve_chain(m); break; case FROM: compare_value(AT_SRC_NET_OFFSET, 2, net); emitop(OP_BRFL); m = chain(0); compare_value(AT_SRC_NODE_OFFSET, 1, node); resolve_chain(m); break; case ANY: compare_value(AT_DST_NET_OFFSET, 2, net); emitop(OP_BRFL); m = chain(0); compare_value(AT_DST_NODE_OFFSET, 1, node); resolve_chain(m); emitop(OP_BRTR); n = chain(0); compare_value(AT_SRC_NET_OFFSET, 2, net); emitop(OP_BRFL); m = chain(0); compare_value(AT_SRC_NODE_OFFSET, 1, node); resolve_chain(m); resolve_chain(n); break; } emitop(OP_OFFSET_POP); } /* * Compare ethernet addresses. The address may * be provided either as a hostname or as a * 6 octet colon-separated address. */ static void etheraddr_match(enum direction which, char *hostname) { uint_t addr; ushort_t *addrp; int to_offset, from_offset; struct ether_addr e, *ep = NULL; int m; /* * First, check the interface type for whether src/dest address * is determinable; if not, retreat early. */ switch (interface->mac_type) { case DL_ETHER: from_offset = ETHERADDRL; to_offset = 0; break; case DL_IB: /* * If an ethernet address is attempted to be used * on an IPoIB interface, flag error. Link address * based filtering is unsupported on IPoIB, so there * is no ipibaddr_match() or parsing support for IPoIB * 20 byte link addresses. */ pr_err("filter option unsupported on media"); break; case DL_FDDI: from_offset = 7; to_offset = 1; break; default: /* * Where do we find "ether" address for FDDI & TR? * XXX can improve? ~sparker */ load_const(1); return; } if (isxdigit(*hostname)) ep = ether_aton(hostname); if (ep == NULL) { if (ether_hostton(hostname, &e)) if (!arp_for_ether(hostname, &e)) pr_err("cannot obtain ether addr for %s", hostname); ep = &e; } memcpy(&addr, (ushort_t *)ep, 4); addrp = (ushort_t *)ep + 2; emitop(OP_OFFSET_ZERO); switch (which) { case TO: compare_value(to_offset, 4, ntohl(addr)); emitop(OP_BRFL); m = chain(0); compare_value(to_offset + 4, 2, ntohs(*addrp)); resolve_chain(m); break; case FROM: compare_value(from_offset, 4, ntohl(addr)); emitop(OP_BRFL); m = chain(0); compare_value(from_offset + 4, 2, ntohs(*addrp)); resolve_chain(m); break; case ANY: compare_value(to_offset, 4, ntohl(addr)); compare_value(to_offset + 4, 2, ntohs(*addrp)); emitop(OP_AND); emitop(OP_BRTR); m = chain(0); compare_value(from_offset, 4, ntohl(addr)); compare_value(from_offset + 4, 2, ntohs(*addrp)); emitop(OP_AND); resolve_chain(m); break; } emitop(OP_OFFSET_POP); } static void ethertype_match(int val) { int ether_offset = interface->network_type_offset; /* * If the user is interested in ethertype VLAN, * then we need to set the offset to the beginning of the packet. * But if the user is interested in another ethertype, * such as IPv4, then we need to take into consideration * the fact that the packet might be VLAN tagged. */ if (interface->mac_type == DL_ETHER || interface->mac_type == DL_CSMACD) { if (val != ETHERTYPE_VLAN) { /* * OP_OFFSET_ETHERTYPE puts us at the ethertype * field whether or not there is a VLAN tag, * so ether_offset goes to zero if we get here. */ emitop(OP_OFFSET_ETHERTYPE); ether_offset = 0; } else { emitop(OP_OFFSET_ZERO); } } compare_value(ether_offset, interface->network_type_len, val); if (interface->mac_type == DL_ETHER || interface->mac_type == DL_CSMACD) { emitop(OP_OFFSET_POP); } } /* * Match a network address. The host part * is masked out. The network address may * be supplied either as a netname or in * IP dotted format. The mask to be used * for the comparison is assumed from the * address format (see comment below). */ static void netaddr_match(enum direction which, char *netname) { uint_t addr; uint_t mask = 0xff000000; uint_t m; struct netent *np; if (isdigit(*netname)) { addr = inet_network(netname); } else { np = getnetbyname(netname); if (np == NULL) pr_err("net %s not known", netname); addr = np->n_net; } /* * Left justify the address and figure * out a mask based on the supplied address. * Set the mask according to the number of zero * low-order bytes. * Note: this works only for whole octet masks. */ if (addr) { while ((addr & ~mask) != 0) { mask |= (mask >> 8); } } emitop(OP_OFFSET_LINK); switch (which) { case TO: compare_value_mask(16, 4, addr, mask); break; case FROM: compare_value_mask(12, 4, addr, mask); break; case ANY: compare_value_mask(12, 4, addr, mask); emitop(OP_BRTR); m = chain(0); compare_value_mask(16, 4, addr, mask); resolve_chain(m); break; } emitop(OP_OFFSET_POP); } /* * Match either a UDP or TCP port number. * The port number may be provided either as * port name as listed in /etc/services ("nntp") or as * the port number itself (2049). */ static void port_match(enum direction which, char *portname) { struct servent *sp; uint_t m, port; if (isdigit(*portname)) { port = atoi(portname); } else { sp = getservbyname(portname, NULL); if (sp == NULL) pr_err("invalid port number or name: %s", portname); port = ntohs(sp->s_port); } emitop(OP_OFFSET_IP); switch (which) { case TO: compare_value(2, 2, port); break; case FROM: compare_value(0, 2, port); break; case ANY: compare_value(2, 2, port); emitop(OP_BRTR); m = chain(0); compare_value(0, 2, port); resolve_chain(m); break; } emitop(OP_OFFSET_POP); } /* * Generate code to match packets with a specific * RPC program number. If the progname is a name * it is converted to a number via /etc/rpc. * The program version and/or procedure may be provided * as extra qualifiers. */ static void rpc_match_prog(enum direction which, char *progname, int vers, int proc) { struct rpcent *rpc; uint_t prog; uint_t m, n; if (isdigit(*progname)) { prog = atoi(progname); } else { rpc = (struct rpcent *)getrpcbyname(progname); if (rpc == NULL) pr_err("invalid program name: %s", progname); prog = rpc->r_number; } emitop(OP_OFFSET_RPC); emitop(OP_BRFL); n = chain(0); compare_value(12, 4, prog); emitop(OP_BRFL); m = chain(0); if (vers >= 0) { compare_value(16, 4, vers); emitop(OP_BRFL); m = chain(m); } if (proc >= 0) { compare_value(20, 4, proc); emitop(OP_BRFL); m = chain(m); } switch (which) { case TO: compare_value(4, 4, CALL); emitop(OP_BRFL); m = chain(m); break; case FROM: compare_value(4, 4, REPLY); emitop(OP_BRFL); m = chain(m); break; } resolve_chain(m); resolve_chain(n); emitop(OP_OFFSET_POP); } /* * Generate code to parse a field specification * and load the value of the field from the packet * onto the operand stack. * The field offset may be specified relative to the * beginning of the ether header, IP header, UDP header, * or TCP header. An optional size specification may * be provided following a colon. If no size is given * one byte is assumed e.g. * * ether[0] The first byte of the ether header * ip[2:2] The second 16 bit field of the IP header */ static void load_field() { int size = 1; int s; if (EQ("ether")) emitop(OP_OFFSET_ZERO); else if (EQ("ip") || EQ("ip6") || EQ("pppoed") || EQ("pppoes")) emitop(OP_OFFSET_LINK); else if (EQ("udp") || EQ("tcp") || EQ("icmp") || EQ("ip-in-ip") || EQ("ah") || EQ("esp")) emitop(OP_OFFSET_IP); else pr_err("invalid field type"); next(); s = opstack; expression(); if (opstack != s + 1) pr_err("invalid field offset"); opstack--; if (*token == ':') { next(); if (tokentype != NUMBER) pr_err("field size expected"); size = tokenval; if (size != 1 && size != 2 && size != 4) pr_err("field size invalid"); next(); } if (*token != ']') pr_err("right bracket expected"); load_value(-1, size); emitop(OP_OFFSET_POP); } /* * Check that the operand stack * contains n arguments */ static void checkstack(int numargs) { if (opstack != numargs) pr_err("invalid expression at \"%s\".", token); } static void primary() { int m, m2, s; for (;;) { if (tokentype == FIELD) { load_field(); opstack++; next(); break; } if (comparison(token)) { opstack++; next(); break; } if (EQ("not") || EQ("!")) { next(); s = opstack; primary(); checkstack(s + 1); emitop(OP_NOT); break; } if (EQ("(")) { next(); s = opstack; expression(); checkstack(s + 1); if (!EQ(")")) pr_err("right paren expected"); next(); } if (EQ("to") || EQ("dst")) { dir = TO; next(); continue; } if (EQ("from") || EQ("src")) { dir = FROM; next(); continue; } if (EQ("ether")) { eaddr = 1; next(); continue; } if (EQ("proto")) { next(); if (tokentype != NUMBER) pr_err("IP proto type expected"); emitop(OP_OFFSET_LINK); compare_value(IPV4_TYPE_HEADER_OFFSET, 1, tokenval); emitop(OP_OFFSET_POP); opstack++; next(); continue; } if (EQ("broadcast")) { /* * Be tricky: FDDI ether dst address begins at * byte one. Since the address is really six * bytes long, this works for FDDI & ethernet. * XXX - Token ring? */ emitop(OP_OFFSET_ZERO); if (interface->mac_type == DL_IB) pr_err("filter option unsupported on media"); compare_value(1, 4, 0xffffffff); emitop(OP_OFFSET_POP); opstack++; next(); break; } if (EQ("multicast")) { /* XXX Token ring? */ emitop(OP_OFFSET_ZERO); if (interface->mac_type == DL_FDDI) { compare_value_mask(1, 1, 0x01, 0x01); } else if (interface->mac_type == DL_IB) { pr_err("filter option unsupported on media"); } else { compare_value_mask(0, 1, 0x01, 0x01); } emitop(OP_OFFSET_POP); opstack++; next(); break; } if (EQ("decnet")) { /* XXX Token ring? */ if (interface->mac_type == DL_FDDI) { load_value(19, 2); /* ether type */ load_const(0x6000); emitop(OP_GE); emitop(OP_BRFL); m = chain(0); load_value(19, 2); /* ether type */ load_const(0x6009); emitop(OP_LE); resolve_chain(m); } else { emitop(OP_OFFSET_ETHERTYPE); load_value(0, 2); /* ether type */ load_const(0x6000); emitop(OP_GE); emitop(OP_BRFL); m = chain(0); load_value(0, 2); /* ether type */ load_const(0x6009); emitop(OP_LE); resolve_chain(m); emitop(OP_OFFSET_POP); } opstack++; next(); break; } if (EQ("vlan-id")) { next(); if (tokentype != NUMBER) pr_err("vlan id expected"); emitop(OP_OFFSET_ZERO); ethertype_match(ETHERTYPE_VLAN); emitop(OP_BRFL); m = chain(0); compare_value_mask(VLAN_ID_OFFSET, 2, tokenval, VLAN_ID_MASK); resolve_chain(m); emitop(OP_OFFSET_POP); opstack++; next(); break; } if (EQ("apple")) { /* * Appletalk also appears in 802.2 * packets, so check for the ethertypes * at offset 12 and 20 in the MAC header. */ ethertype_match(ETHERTYPE_AT); emitop(OP_BRTR); m = chain(0); ethertype_match(ETHERTYPE_AARP); emitop(OP_BRTR); m = chain(m); compare_value(20, 2, ETHERTYPE_AT); /* 802.2 */ emitop(OP_BRTR); m = chain(m); compare_value(20, 2, ETHERTYPE_AARP); /* 802.2 */ resolve_chain(m); opstack++; next(); break; } if (EQ("vlan")) { ethertype_match(ETHERTYPE_VLAN); compare_value_mask(VLAN_ID_OFFSET, 2, 0, VLAN_ID_MASK); emitop(OP_NOT); emitop(OP_AND); opstack++; next(); break; } if (EQ("bootp") || EQ("dhcp")) { ethertype_match(interface->network_type_ip); emitop(OP_BRFL); m = chain(0); emitop(OP_OFFSET_LINK); compare_value(9, 1, IPPROTO_UDP); emitop(OP_OFFSET_POP); emitop(OP_BRFL); m = chain(m); emitop(OP_OFFSET_IP); compare_value(0, 4, (IPPORT_BOOTPS << 16) | IPPORT_BOOTPC); emitop(OP_BRTR); m2 = chain(0); compare_value(0, 4, (IPPORT_BOOTPC << 16) | IPPORT_BOOTPS); resolve_chain(m2); emitop(OP_OFFSET_POP); resolve_chain(m); opstack++; dir = ANY; next(); break; } if (EQ("dhcp6")) { ethertype_match(interface->network_type_ipv6); emitop(OP_BRFL); m = chain(0); emitop(OP_OFFSET_LINK); compare_value(6, 1, IPPROTO_UDP); emitop(OP_OFFSET_POP); emitop(OP_BRFL); m = chain(m); emitop(OP_OFFSET_IP); compare_value(2, 2, IPPORT_DHCPV6S); emitop(OP_BRTR); m2 = chain(0); compare_value(2, 2, IPPORT_DHCPV6C); resolve_chain(m2); emitop(OP_OFFSET_POP); resolve_chain(m); opstack++; dir = ANY; next(); break; } if (EQ("ethertype")) { next(); if (tokentype != NUMBER) pr_err("ether type expected"); ethertype_match(tokenval); opstack++; next(); break; } if (EQ("pppoe")) { ethertype_match(ETHERTYPE_PPPOED); ethertype_match(ETHERTYPE_PPPOES); emitop(OP_OR); opstack++; next(); break; } if (EQ("inet")) { next(); if (EQ("host")) next(); if (tokentype != ALPHA && tokentype != ADDR_IP) pr_err("host/IPv4 addr expected after inet"); ipaddr_match(dir, token, IPV4_ONLY); opstack++; next(); break; } if (EQ("inet6")) { next(); if (EQ("host")) next(); if (tokentype != ALPHA && tokentype != ADDR_IP6) pr_err("host/IPv6 addr expected after inet6"); ipaddr_match(dir, token, IPV6_ONLY); opstack++; next(); break; } if (EQ("length")) { emitop(OP_LOAD_LENGTH); opstack++; next(); break; } if (EQ("less")) { next(); if (tokentype != NUMBER) pr_err("packet length expected"); emitop(OP_LOAD_LENGTH); load_const(tokenval); emitop(OP_LT); opstack++; next(); break; } if (EQ("greater")) { next(); if (tokentype != NUMBER) pr_err("packet length expected"); emitop(OP_LOAD_LENGTH); load_const(tokenval); emitop(OP_GT); opstack++; next(); break; } if (EQ("nofrag")) { emitop(OP_OFFSET_LINK); compare_value_mask(6, 2, 0, 0x1fff); emitop(OP_OFFSET_POP); emitop(OP_BRFL); m = chain(0); ethertype_match(interface->network_type_ip); resolve_chain(m); opstack++; next(); break; } if (EQ("net") || EQ("dstnet") || EQ("srcnet")) { if (EQ("dstnet")) dir = TO; else if (EQ("srcnet")) dir = FROM; next(); netaddr_match(dir, token); dir = ANY; opstack++; next(); break; } if (EQ("port") || EQ("srcport") || EQ("dstport")) { if (EQ("dstport")) dir = TO; else if (EQ("srcport")) dir = FROM; next(); port_match(dir, token); dir = ANY; opstack++; next(); break; } if (EQ("rpc")) { uint_t vers, proc; char savetoken[32]; vers = proc = -1; next(); (void) strlcpy(savetoken, token, sizeof (savetoken)); next(); if (*token == ',') { next(); if (tokentype != NUMBER) pr_err("version number expected"); vers = tokenval; next(); } if (*token == ',') { next(); if (tokentype != NUMBER) pr_err("proc number expected"); proc = tokenval; next(); } rpc_match_prog(dir, savetoken, vers, proc); dir = ANY; opstack++; break; } if (EQ("slp")) { /* filter out TCP handshakes */ emitop(OP_OFFSET_LINK); compare_value(9, 1, IPPROTO_TCP); emitop(OP_LOAD_CONST); emitval(52); emitop(OP_LOAD_CONST); emitval(2); emitop(OP_LOAD_SHORT); emitop(OP_GE); emitop(OP_AND); /* proto == TCP && len < 52 */ emitop(OP_NOT); emitop(OP_BRFL); /* pkt too short to be a SLP call */ m = chain(0); emitop(OP_OFFSET_POP); emitop(OP_OFFSET_SLP); resolve_chain(m); opstack++; next(); break; } if (EQ("ldap")) { dir = ANY; port_match(dir, "ldap"); opstack++; next(); break; } if (EQ("and") || EQ("or")) { break; } if (EQ("zone")) { next(); if (tokentype != NUMBER) pr_err("zoneid expected"); zone_match(dir, BE_32((uint32_t)(tokenval))); opstack++; next(); break; } if (EQ("gateway")) { next(); if (eaddr || tokentype != ALPHA) pr_err("hostname required: %s", token); etheraddr_match(dir, token); dir = ANY; emitop(OP_BRFL); m = chain(0); ipaddr_match(dir, token, IPV4_AND_IPV6); emitop(OP_NOT); resolve_chain(m); opstack++; next(); } if (EQ("host") || EQ("between") || tokentype == ALPHA || /* assume its a hostname */ tokentype == ADDR_IP || tokentype == ADDR_IP6 || tokentype == ADDR_AT || tokentype == ADDR_ETHER) { if (EQ("host") || EQ("between")) next(); if (eaddr || tokentype == ADDR_ETHER) { etheraddr_match(dir, token); } else if (tokentype == ALPHA) { ipaddr_match(dir, token, IPV4_AND_IPV6); } else if (tokentype == ADDR_AT) { ataddr_match(dir, token); } else if (tokentype == ADDR_IP) { ipaddr_match(dir, token, IPV4_ONLY); } else { ipaddr_match(dir, token, IPV6_ONLY); } dir = ANY; eaddr = 0; opstack++; next(); break; } if (tokentype == NUMBER) { load_const(tokenval); opstack++; next(); break; } break; /* unknown token */ } } struct optable { char *op_tok; enum optype op_type; }; static struct optable mulops[] = { "*", OP_MUL, "/", OP_DIV, "%", OP_REM, "&", OP_AND, "", OP_STOP, }; static struct optable addops[] = { "+", OP_ADD, "-", OP_SUB, "|", OP_OR, "^", OP_XOR, "", OP_STOP, }; static struct optable compareops[] = { "==", OP_EQ, "=", OP_EQ, "!=", OP_NE, ">", OP_GT, ">=", OP_GE, "<", OP_LT, "<=", OP_LE, "", OP_STOP, }; /* * Using the table, find the operator * that corresponds to the token. * Return 0 if not found. */ static int find_op(char *tok, struct optable *table) { struct optable *op; for (op = table; *op->op_tok; op++) { if (strcmp(tok, op->op_tok) == 0) return (op->op_type); } return (0); } static void expr_mul() { int op; int s = opstack; primary(); while (op = find_op(token, mulops)) { next(); primary(); checkstack(s + 2); emitop(op); opstack--; } } static void expr_add() { int op, s = opstack; expr_mul(); while (op = find_op(token, addops)) { next(); expr_mul(); checkstack(s + 2); emitop(op); opstack--; } } static void expr_compare() { int op, s = opstack; expr_add(); while (op = find_op(token, compareops)) { next(); expr_add(); checkstack(s + 2); emitop(op); opstack--; } } /* * Alternation ("and") is difficult because * an implied "and" is acknowledge between * two adjacent primaries. Just keep calling * the lower-level expression routine until * no value is added to the opstack. */ static void alternation() { int m = 0; int s = opstack; expr_compare(); checkstack(s + 1); for (;;) { if (EQ("and")) next(); emitop(OP_BRFL); m = chain(m); expr_compare(); if (opstack != s + 2) break; opstack--; } unemit(2); resolve_chain(m); } static void expression() { int m = 0; int s = opstack; alternation(); while (EQ("or") || EQ(",")) { emitop(OP_BRTR); m = chain(m); next(); alternation(); checkstack(s + 2); opstack--; } resolve_chain(m); } /* * Take n args from the argv list * and concatenate them into a single string. */ char * concat_args(char **argv, int argc) { int i, len; char *str, *p; /* First add the lengths of all the strings */ len = 0; for (i = 0; i < argc; i++) len += strlen(argv[i]) + 1; /* allocate the big string */ str = (char *)malloc(len); if (str == NULL) pr_err("no mem"); p = str; /* * Concat the strings into the big * string using a space as separator */ for (i = 0; i < argc; i++) { strcpy(p, argv[i]); p += strlen(p); *p++ = ' '; } *--p = '\0'; return (str); } /* * Take the expression in the string "expr" * and compile it into the code array. * Print the generated code if the print * arg is set. */ void compile(char *expr, int print) { expr = strdup(expr); if (expr == NULL) pr_err("no mem"); curr_op = oplist; tkp = expr; dir = ANY; next(); if (tokentype != EOL) expression(); emitop(OP_STOP); if (tokentype != EOL) pr_err("invalid expression"); optimize(oplist); if (print) codeprint(); } /* * Lookup hostname in the arp cache. */ boolean_t arp_for_ether(char *hostname, struct ether_addr *ep) { struct arpreq ar; struct hostent *hp; struct sockaddr_in *sin; int error_num; int s; memset(&ar, 0, sizeof (ar)); sin = (struct sockaddr_in *)&ar.arp_pa; sin->sin_family = AF_INET; hp = getipnodebyname(hostname, AF_INET, 0, &error_num); if (hp == NULL) { return (B_FALSE); } memcpy(&sin->sin_addr, hp->h_addr, sizeof (sin->sin_addr)); s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) { return (B_FALSE); } if (ioctl(s, SIOCGARP, &ar) < 0) { close(s); return (B_FALSE); } close(s); memcpy(ep->ether_addr_octet, ar.arp_ha.sa_data, sizeof (*ep)); return (B_TRUE); }