/* * Copyright (c) 2000-2004 QoSient, LLC * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * */ /* * Copyright (c) 1990, 1991, 1992, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that: (1) source code distributions * retain the above copyright notice and this paragraph in its entirety, (2) * distributions including binary code include the above copyright notice and * this paragraph in its entirety in the documentation or other materials * provided with the distribution, and (3) all advertising materials mentioning * features or use of this software display the following acknowledgement: * ``This product includes software developed by the University of California, * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of * the University nor the names of its contributors may be used to endorse * or promote products derived from this software without specific prior * written permission. * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. */ #if defined(HAVE_SOLARIS) || defined(__FreeBSD__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__APPLE_CC__) #include #include #endif #include #include #include #if !defined(__OpenBSD__) || !defined(_NET_IF_H_) #include #define _NET_IF_H_ #endif #include #include #include #include #include #include #include #include #include #ifndef __GNUC__ #define inline #endif extern void ArgusLog (int, char *, ...); #define JMP(c) ((c)|BPF_JMP|BPF_K) static jmp_buf top_ctx; static u_int off_nl = 0; static int alloc_reg(void); static void free_reg(int); static struct block *root; /* * We divy out chunks of memory rather than call malloc each time so * we don't have to worry about leaking memory. It's probably * not a big deal if all this memory was wasted but it this ever * goes into a library that would probably not be a good idea. */ #define NCHUNKS 16 #define CHUNK0SIZE 1024 struct chunk { u_int n_left; void *m; }; static struct chunk chunks[NCHUNKS]; static int cur_chunk; static void *newchunk(u_int); static void freechunks(void); static struct block *new_block(int); static struct slist *new_stmt(int); static struct block *Argusgen_retblk(int); static void syntax(void); static void backpatch(struct block *, struct block *); static void merge(struct block *, struct block *); static struct block *Argusgen_cmp(u_int, u_int, u_int); static struct block *Argusgen_mcmp(u_int, u_int, u_int, u_int); static struct block *Argusgen_bcmp(u_int, u_int, u_char *); static struct block *Argusgen_prototype(u_int); static struct block *Argusgen_hostop(u_int, u_int, int, u_int, u_int, u_int); static struct block *Argusgen_ehostop(u_char *, int); static struct block *Argusgen_host(u_int, u_int, int, int); static struct block *Argusgen_gateway(u_char *, u_int **, int, int); static struct block *Argusgen_portatom(int, long); struct block *Argusgen_portop(int, int, int); static struct block *Argusgen_port(int, u_int, int); static int lookup_proto(char *, int); static struct block *Argusgen_proto(int, int, int); static struct block *Argusgen_ttl(int, int); static struct block *Argusgen_tos(int, int); static u_int net_mask(u_int *); static struct slist *xfer_to_x(struct arth *); static struct slist *xfer_to_a(struct arth *); static struct block *Argusgen_len(int, int); static void * newchunk(n) u_int n; { struct chunk *cp; int k, size; /* XXX Round up to nearest long. */ n = (n + sizeof(long) - 1) & ~(sizeof(long) - 1); cp = &chunks[cur_chunk]; if (n > cp->n_left) { ++cp, k = ++cur_chunk; if (k >= NCHUNKS) ArgusLog(LOG_ERR,"out of memory"); size = CHUNK0SIZE << k; cp->m = (void *)malloc(size); memset((char *)cp->m, 0, size); cp->n_left = size; if (n > size) ArgusLog(LOG_ERR,"out of memory"); } cp->n_left -= n; return (void *)((char *)cp->m + cp->n_left); } static void freechunks() { int i; for (i = 0; i < NCHUNKS; ++i) if (chunks[i].m) free(chunks[i].m); } /* * A strdup whose allocations are freed after code generation is over. */ char * Argussdup(s) char *s; { int n = strlen(s) + 1; char *cp = newchunk(n); strcpy(cp, s); return (cp); } static struct block * new_block(code) int code; { struct block *p; p = (struct block *)newchunk(sizeof(*p)); p->s.code = code; p->head = p; return p; } static struct slist * new_stmt(code) int code; { struct slist *p; p = (struct slist *)newchunk(sizeof(*p)); p->s.code = code; return p; } static struct block * Argusgen_retblk(v) int v; { struct block *b = new_block(BPF_RET|BPF_K); b->s.k = v; return b; } static void syntax() { ArgusLog(LOG_ERR,"syntax error in filter expression"); } static u_int ArgusNetMask; static int snaplen; int ArgusFilterCompile(struct bpf_program *program, char *buf, int optimize, unsigned int mask) { extern int argus_n_errors; int len; if (setjmp(top_ctx)) return (-1); ArgusNetMask = mask; snaplen = 96; argus_lex_init(buf ? buf : ""); argus_parse(); if (argus_n_errors) syntax(); if (root == NULL) root = Argusgen_retblk(snaplen); if (optimize) { Argusbpf_optimize(&root); if (root == NULL || (root->s.code == (BPF_RET|BPF_K) && root->s.k == 0)) ArgusLog(LOG_ERR,"expression rejects all packets"); } program->bf_insns = Argusicode_to_fcode(root, &len); program->bf_len = len; freechunks(); return (0); } /* * Backpatch the blocks in 'list' to 'target'. The 'sense' field indicates * which of the jt and jf fields has been resolved and which is a pointer * back to another unresolved block (or nil). At least one of the fields * in each block is already resolved. */ static void backpatch(list, target) struct block *list, *target; { struct block *next; while (list) { if (!list->sense) { next = JT(list); JT(list) = target; } else { next = JF(list); JF(list) = target; } list = next; } } /* * Merge the lists in b0 and b1, using the 'sense' field to indicate * which of jt and jf is the link. */ static void merge(b0, b1) struct block *b0, *b1; { register struct block **p = &b0; /* Find end of list. */ while (*p) p = !((*p)->sense) ? &JT(*p) : &JF(*p); /* Concatenate the lists. */ *p = b1; } void Argusfinish_parse(p) struct block *p; { backpatch(p, Argusgen_retblk(snaplen)); p->sense = !p->sense; backpatch(p, Argusgen_retblk(0)); root = p->head; } void Argusgen_and(b0, b1) struct block *b0, *b1; { if (b0 != b1) { backpatch(b0, b1->head); b0->sense = !b0->sense; b1->sense = !b1->sense; merge(b1, b0); b1->sense = !b1->sense; b1->head = b0->head; } } void Argusgen_or(b0, b1) struct block *b0, *b1; { if (b0 != b1) { b0->sense = !b0->sense; backpatch(b0, b1->head); b0->sense = !b0->sense; merge(b1, b0); b1->head = b0->head; } } void Argusgen_not(b) struct block *b; { b->sense = !b->sense; } static struct block * Argusgen_cmp(offset, size, v) u_int offset, size; u_int v; { struct slist *s; struct block *b; s = new_stmt(BPF_LD|BPF_ABS|size); s->s.k = offset; b = new_block(JMP(BPF_JEQ)); b->stmts = s; b->s.k = v; return b; } static struct block * Argusgen_mcmp(offset, size, v, mask) u_int offset, size; u_int v; u_int mask; { struct block *b = Argusgen_cmp(offset, size, v); struct slist *s; if (mask != 0xffffffff) { s = new_stmt(BPF_ALU|BPF_AND|BPF_K); s->s.k = mask; b->stmts->next = s; } return b; } static struct block * Argusgen_bcmp(offset, size, v) u_int offset, size; u_char *v; { struct block *b, *tmp; b = NULL; while (size >= 4) { u_char *p = &v[size - 4]; u_int w = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3]; tmp = Argusgen_cmp(offset + size - 4, BPF_W, w); if (b != NULL) Argusgen_and(b, tmp); b = tmp; size -= 4; } while (size >= 2) { u_char *p = &v[size - 2]; u_int w = (p[0] << 8) | p[1]; tmp = Argusgen_cmp(offset + size - 2, BPF_H, w); if (b != NULL) Argusgen_and(b, tmp); b = tmp; size -= 2; } if (size > 0) { tmp = Argusgen_cmp(offset, BPF_B, (u_int)v[0]); if (b != NULL) Argusgen_and(b, tmp); b = tmp; } return b; } static struct block * Argusgen_espstatustype(unsigned int proto) { struct block *b0 = NULL, *b1 = NULL; b1 = Argusgen_prototype(IPPROTO_ESP); switch (proto) { case ARGUS_SRC_PKTS_RETRANS: b0 = Argusgen_cmp(116, BPF_W, 0); Argusgen_not(b0); break; case ARGUS_DST_PKTS_RETRANS: b0 = Argusgen_cmp(128, BPF_W, 0); Argusgen_not(b0); break; } if (b0) Argusgen_and(b0, b1); return (b1); } static struct block * Argusgen_tcpstatustype(unsigned int proto) { struct block *b0, *b1; unsigned int value = proto; b0 = Argusgen_prototype(IPPROTO_TCP); switch (proto) { #define ARGUS_ECN_CONGESTED 0xC000 /* SRC_CONGESTED | DST_CONGESTED */ case ARGUS_SRC_CONGESTED: case ARGUS_DST_CONGESTED: case ARGUS_SRC_RESET: case ARGUS_DST_RESET: case ARGUS_SRC_WINDOW_SHUT: case ARGUS_DST_WINDOW_SHUT: case ARGUS_NORMAL_CLOSE: case ARGUS_SAW_SYN: case ARGUS_SAW_SYN_SENT: case ARGUS_CON_ESTABLISHED: case ARGUS_CLOSE_WAITING: case ARGUS_SRC_PKTS_RETRANS: case ARGUS_DST_PKTS_RETRANS: default: b1 = Argusgen_mcmp(108, BPF_W, value, value); break; } Argusgen_and(b0, b1); return (b1); } static struct block * Argusgen_causetype(unsigned int cause) { struct block *b0 = NULL; switch (cause) { case ARGUS_START: case ARGUS_STATUS: case ARGUS_STOP: case ARGUS_TIMEOUT: b0 = Argusgen_mcmp(1, BPF_B, (u_int) cause, cause); break; } return (b0); } static struct block * Argusgen_recordtype(unsigned int proto) { struct block *b0 = NULL; switch (proto) { case ARGUS_MAR: case ARGUS_FAR: case ARGUS_DATASUP: b0 = Argusgen_mcmp(0, BPF_B, (u_int) proto, proto); break; } return (b0); } static struct block * Argusgen_Farstatustype(unsigned int proto) { struct block *b0 = NULL, *b1 = NULL; switch (proto) { case ARGUS_MULTIADDR: case ARGUS_ICMPUNREACH_MAPPED: case ARGUS_ICMPREDIREC_MAPPED: case ARGUS_ICMPTIMXCED_MAPPED: b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); b1 = Argusgen_mcmp(18, BPF_H, (u_int) proto, proto); Argusgen_and(b0, b1); break; case ARGUS_VLAN: case ARGUS_MPLS: case ARGUS_MERGED: case ARGUS_DETAIL: case ARGUS_CONNECTED: b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); b1 = Argusgen_mcmp(4, BPF_W, (u_int) proto, proto); Argusgen_and(b0, b1); break; default: b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); b1 = Argusgen_cmp(6, BPF_H, (u_int) proto); Argusgen_and(b0, b1); break; } return (b1); } static struct block * Argusgen_FarAttrtype(unsigned int proto) { struct block *b0 = NULL, *b1 = NULL; switch (proto) { case ARGUS_FRAGMENTS: b0 = Argusgen_mcmp(56, BPF_H, (u_int) proto, proto); b1 = Argusgen_mcmp(58, BPF_H, (u_int) proto, proto); Argusgen_or(b0, b1); b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); Argusgen_and(b0, b1); break; } return (b1); } static struct block * Argusgen_prototype(unsigned int proto) { struct block *b0, *b1; switch (proto) { case IPPROTO_RTP: b0 = Argusgen_cmp(48, BPF_B, (u_int) IPPROTO_UDP); b1 = Argusgen_cmp(49, BPF_B, (u_int) ARGUS_RTP_FLOWTAG); Argusgen_and(b0, b1); break; default: /* all the flow protocols */ b1 = Argusgen_cmp(48, BPF_B, (u_int) proto); break; } return b1; } static struct block * Argusgen_hostop(unsigned int addr, unsigned int mask, int dir, unsigned int proto, unsigned int src_off, unsigned int dst_off) { struct block *b0, *b1; u_int offset; switch (dir) { case Q_SRC: offset = src_off; break; case Q_DST: offset = dst_off; break; case Q_AND: b0 = Argusgen_hostop(addr, mask, Q_SRC, proto, src_off, dst_off); b1 = Argusgen_hostop(addr, mask, Q_DST, proto, src_off, dst_off); Argusgen_and(b0, b1); return b1; case Q_OR: case Q_DEFAULT: b0 = Argusgen_hostop(addr, mask, Q_SRC, proto, src_off, dst_off); b1 = Argusgen_hostop(addr, mask, Q_DST, proto, src_off, dst_off); Argusgen_or(b0, b1); return b1; default: abort(); } b0 = Argusgen_Farstatustype(proto); b1 = Argusgen_mcmp(offset, BPF_W, (u_int)addr, mask); Argusgen_and(b0, b1); return b1; } static struct block * Argusgen_ehostop(eaddr, dir) u_char *eaddr; int dir; { struct block *b0, *b1; switch (dir) { case Q_SRC: return Argusgen_bcmp (92, 6, eaddr); case Q_DST: return Argusgen_bcmp (98, 6, eaddr); case Q_AND: b0 = Argusgen_ehostop(eaddr, Q_SRC); b1 = Argusgen_ehostop(eaddr, Q_DST); Argusgen_and(b0, b1); return b1; case Q_DEFAULT: case Q_OR: b0 = Argusgen_ehostop(eaddr, Q_SRC); b1 = Argusgen_ehostop(eaddr, Q_DST); Argusgen_or(b0, b1); return b1; } abort(); /* NOTREACHED */ } static struct block * Argusgen_host(addr, mask, proto, dir) u_int addr; u_int mask; int proto; int dir; { struct block *b0, *b1 = NULL; switch (proto) { case Q_DEFAULT: b0 = Argusgen_hostop(addr, mask, dir, ETHERTYPE_ARP, 40, 44); b1 = Argusgen_hostop(addr, mask, dir, ETHERTYPE_IP, 40, 44); Argusgen_or(b0, b1); break; case Q_IP: b1 = Argusgen_hostop(addr, mask, dir, ETHERTYPE_IP, 40, 44); break; case Q_ARP: b1 = Argusgen_hostop(addr, mask, dir, ETHERTYPE_ARP, 40, 44); break; case Q_RARP: b1 = Argusgen_hostop(addr, mask, dir, ETHERTYPE_REVARP, 40, 44); break; case Q_TCP: ArgusLog(LOG_ERR,"'tcp' modifier applied to host"); case Q_UDP: ArgusLog(LOG_ERR,"'udp' modifier applied to host"); case Q_RTP: ArgusLog(LOG_ERR,"'rtp' modifier applied to host"); case Q_ICMP: ArgusLog(LOG_ERR,"'icmp' modifier applied to host"); default: abort(); } return (b1); } static struct block * Argusgen_gateway(eaddr, alist, proto, dir) u_char *eaddr; u_int **alist; int proto; int dir; { struct block *b0, *b1 = NULL, *tmp; if (dir != 0) ArgusLog(LOG_ERR,"direction applied to 'gateway'"); switch (proto) { case Q_DEFAULT: case Q_IP: case Q_ARP: case Q_RARP: b0 = Argusgen_ehostop(eaddr, Q_OR); b1 = Argusgen_host(**alist++, 0xffffffffL, proto, Q_OR); while (*alist) { tmp = Argusgen_host(**alist++, 0xffffffffL, proto, Q_OR); Argusgen_or(b1, tmp); b1 = tmp; } Argusgen_not(b1); Argusgen_and(b0, b1); break; default: ArgusLog(LOG_ERR,"illegal modifier of 'gateway'"); } return b1; } struct block * Argusgen_proto_abbrev(proto) int proto; { struct block *b0, *b1; switch (proto) { case Q_TCP: b1 = Argusgen_prototype(IPPROTO_TCP); break; case Q_ESP: b1 = Argusgen_prototype(IPPROTO_ESP); break; case Q_RTP: b1 = Argusgen_prototype(IPPROTO_RTP); break; case Q_UDP: b0 = Argusgen_Farstatustype(ETHERTYPE_IP); b1 = Argusgen_prototype(IPPROTO_UDP); Argusgen_and(b0, b1); break; case Q_ICMP: b1 = Argusgen_prototype(IPPROTO_ICMP); break; case Q_IGMP: b1 = Argusgen_prototype(IPPROTO_IGMP); break; #ifndef IPPROTO_IGRP #define IPPROTO_IGRP 9 #endif case Q_IGRP: b1 = Argusgen_prototype(IPPROTO_IGRP); break; case Q_MPLS: b1 = Argusgen_Farstatustype(ARGUS_MPLS); break; case Q_VLAN: b1 = Argusgen_Farstatustype(ARGUS_VLAN); break; case Q_RARP: b1 = Argusgen_Farstatustype(ETHERTYPE_REVARP); break; case Q_ARP: b1 = Argusgen_Farstatustype(ETHERTYPE_ARP); break; case Q_IP: b1 = Argusgen_Farstatustype(ETHERTYPE_IP); break; case Q_MAN: b1 = Argusgen_recordtype(ARGUS_MAR); break; case Q_MULTIPATH: b1 = Argusgen_Farstatustype(ARGUS_MULTIADDR); break; case Q_FRAG: b1 = Argusgen_FarAttrtype(ARGUS_FRAGMENTS); break; case Q_CONNECTED: case Q_ESTABLISHED: b1 = Argusgen_Farstatustype(ARGUS_CONNECTED); break; case Q_MERGED: b1 = Argusgen_Farstatustype(ARGUS_MERGED); break; case Q_DETAIL: b1 = Argusgen_Farstatustype(ARGUS_DETAIL); break; case Q_ECHO: b0 = Argusgen_cmp(50, BPF_B, (u_int) 0x08); b1 = Argusgen_cmp(50, BPF_B, (u_int) 0x00); Argusgen_or(b0, b1); b0 = Argusgen_prototype(IPPROTO_ICMP); Argusgen_and(b0, b1); break; case Q_UNREACH: b1 = Argusgen_prototype(IPPROTO_ICMP); b0 = Argusgen_cmp(50, BPF_B, (u_int) 0x03); Argusgen_and(b0, b1); b0 = Argusgen_Farstatustype(ARGUS_ICMPUNREACH_MAPPED); Argusgen_or(b0, b1); break; case Q_REDIRECT: b1 = Argusgen_prototype(IPPROTO_ICMP); b0 = Argusgen_cmp(50, BPF_B, (u_int) 0x05); Argusgen_and(b0, b1); b0 = Argusgen_Farstatustype(ARGUS_ICMPREDIREC_MAPPED); Argusgen_or(b0, b1); break; case Q_TIMEXED: b1 = Argusgen_prototype(IPPROTO_ICMP); b0 = Argusgen_cmp(50, BPF_B, (u_int) 0x0B); Argusgen_and(b0, b1); b0 = Argusgen_Farstatustype(ARGUS_ICMPTIMXCED_MAPPED); Argusgen_or(b0, b1); break; case Q_TIMEDOUT: b1 = Argusgen_causetype(ARGUS_TIMEOUT); break; case Q_RETRANS: b0 = Argusgen_espstatustype(ARGUS_SRC_PKTS_RETRANS); b1 = Argusgen_tcpstatustype(ARGUS_SRC_PKTS_RETRANS); Argusgen_or(b0, b1); b0 = Argusgen_espstatustype(ARGUS_DST_PKTS_RETRANS); Argusgen_or(b0, b1); b0 = Argusgen_tcpstatustype(ARGUS_DST_PKTS_RETRANS); Argusgen_or(b0, b1); break; case Q_SRCRETRANS: b0 = Argusgen_espstatustype(ARGUS_SRC_PKTS_RETRANS); b1 = Argusgen_tcpstatustype(ARGUS_SRC_PKTS_RETRANS); Argusgen_or(b0, b1); break; case Q_DSTRETRANS: b0 = Argusgen_espstatustype(ARGUS_DST_PKTS_RETRANS); b1 = Argusgen_tcpstatustype(ARGUS_DST_PKTS_RETRANS); Argusgen_or(b0, b1); break; case Q_SYN: b1 = Argusgen_tcpstatustype(ARGUS_SAW_SYN); break; case Q_SYNACK: b1 = Argusgen_tcpstatustype(ARGUS_SAW_SYN_SENT); break; case Q_DATA: b1 = Argusgen_tcpstatustype(ARGUS_CON_ESTABLISHED); break; case Q_FIN: b1 = Argusgen_tcpstatustype(ARGUS_FIN); break; case Q_FINACK: b1 = Argusgen_tcpstatustype(ARGUS_FIN_ACK); break; case Q_WAIT: b1 = Argusgen_tcpstatustype(ARGUS_CLOSE_WAITING); break; case Q_NORMAL: b1 = Argusgen_tcpstatustype(ARGUS_NORMAL_CLOSE); break; case Q_LINK: ArgusLog(LOG_ERR,"link layer applied in wrong context"); default: abort(); } return b1; } static struct block * Argusgen_ttlatom(int off, u_int v) { return Argusgen_cmp(60 + off, BPF_B, (u_int)v); } static struct block * Argusgen_tosatom(int off, u_int v) { return Argusgen_cmp(62 + off, BPF_B, v); } static struct block * Argusgen_portatom(off, v) int off; long v; { return Argusgen_cmp(50 + off, BPF_H, (u_int)v); } struct block * Argusgen_portop(port, proto, dir) int port, proto, dir; { struct block *b0, *b1, *tmp; /* ip proto 'proto' */ b0 = Argusgen_prototype(proto); switch (dir) { case Q_SRC: b1 = Argusgen_portatom(0, (long)port); break; case Q_DST: b1 = Argusgen_portatom(2, (long)port); break; case Q_OR: case Q_DEFAULT: tmp = Argusgen_portatom(0, (long)port); b1 = Argusgen_portatom(2, (long)port); Argusgen_or(tmp, b1); break; case Q_AND: tmp = Argusgen_portatom(0, (long)port); b1 = Argusgen_portatom(2, (long)port); Argusgen_and(tmp, b1); break; default: abort(); } Argusgen_and(b0, b1); return b1; } static struct block * Argusgen_port(port, ip_proto, dir) int port; u_int ip_proto; int dir; { struct block *b1, *tmp; switch (ip_proto) { case IPPROTO_TCP: b1 = Argusgen_portop(port, IPPROTO_TCP, dir); break; case IPPROTO_UDP: b1 = Argusgen_portop(port, IPPROTO_UDP, dir); break; case IPPROTO_RTP: tmp = Argusgen_portop(port, IPPROTO_UDP, dir); b1 = Argusgen_portop(port, IPPROTO_RTP, dir); Argusgen_and(tmp, b1); break; case PROTO_UNDEF: tmp = Argusgen_portop(port, IPPROTO_TCP, dir); b1 = Argusgen_portop(port, IPPROTO_UDP, dir); Argusgen_or(tmp, b1); break; default: abort(); } return b1; } static int lookup_proto(name, proto) char *name; int proto; { int v = 0; switch (proto) { case Q_DEFAULT: case Q_IP: v = argus_nametoproto(name); if (v == PROTO_UNDEF) ArgusLog(LOG_ERR,"unknown proto '%s'", name); break; case Q_LINK: /* XXX should look up h/w protocol type based on linktype */ v = argus_nametoeproto(name); if (v == PROTO_UNDEF) ArgusLog(LOG_ERR,"unknown ether proto '%s'", name); break; case Q_MAN: ArgusLog (LOG_ERR, "man proto called '%s'", name); break; default: v = PROTO_UNDEF; break; } return v; } static struct block * Argusgen_proto(v, proto, dir) int v; int proto; int dir; { struct block *b0, *b1; if (dir != Q_DEFAULT) ArgusLog(LOG_ERR,"direction applied to 'proto'"); switch (proto) { case Q_DEFAULT: case Q_IP: b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); b1 = Argusgen_Farstatustype(ETHERTYPE_IP); Argusgen_and(b0, b1); b0 = Argusgen_prototype(v); Argusgen_and(b0, b1); return b1; case Q_ARP: b0 = Argusgen_recordtype(ARGUS_MAR); Argusgen_not(b0); b1 = Argusgen_Farstatustype(ETHERTYPE_ARP); Argusgen_and(b0, b1); return b1; case Q_RARP: ArgusLog(LOG_ERR,"rarp does not encapsulate another protocol"); /* NOTREACHED */ case Q_MAN: /* case Q_DETAIL: */ return Argusgen_recordtype(ARGUS_MAR); case Q_LINK: return Argusgen_Farstatustype(v); case Q_UDP: ArgusLog(LOG_ERR,"'udp proto' is bogus"); /* NOTREACHED */ case Q_RTP: ArgusLog(LOG_ERR,"'rtp proto' is bogus"); /* NOTREACHED */ case Q_TCP: ArgusLog(LOG_ERR,"'tcp proto' is bogus"); /* NOTREACHED */ case Q_ICMP: ArgusLog(LOG_ERR,"'icmp proto' is bogus"); /* NOTREACHED */ case Q_IGMP: ArgusLog(LOG_ERR,"'igmp proto' is bogus"); /* NOTREACHED */ default: abort(); /* NOTREACHED */ } /* NOTREACHED */ } static struct block * Argusgen_ttl(int v, int dir) { struct block *b0, *b1 = NULL, *tmp; b0 = Argusgen_proto(v, Q_IP, Q_DEFAULT); switch (dir) { case Q_SRC: b1 = Argusgen_ttlatom(0, (u_int)v); break; case Q_DST: b1 = Argusgen_ttlatom(1, (u_int)v); break; case Q_OR: case Q_DEFAULT: tmp = Argusgen_ttlatom(0, (u_int)v); b1 = Argusgen_ttlatom(1, (u_int)v); Argusgen_or(tmp, b1); break; case Q_AND: tmp = Argusgen_ttlatom(0, (u_int)v); b1 = Argusgen_ttlatom(1, (u_int)v); Argusgen_and(tmp, b1); break; default: abort(); } Argusgen_and(b0, b1); return b1; } static struct block * Argusgen_tos(int v, int dir) { struct block *b0, *b1 = NULL, *tmp; b0 = Argusgen_proto(v, Q_IP, Q_DEFAULT); switch (dir) { case Q_SRC: b1 = Argusgen_tosatom(0, (u_int)v); break; case Q_DST: b1 = Argusgen_tosatom(1, (u_int)v); break; case Q_OR: case Q_DEFAULT: tmp = Argusgen_tosatom(0, (u_int)v); b1 = Argusgen_tosatom(1, (u_int)v); Argusgen_or(tmp, b1); break; case Q_AND: tmp = Argusgen_tosatom(0, (u_int)v); b1 = Argusgen_tosatom(1, (u_int)v); Argusgen_and(tmp, b1); break; default: abort(); } Argusgen_and(b0, b1); return b1; } /* * Left justify 'addr' and return its resulting network mask. */ static u_int net_mask(addr) u_int *addr; { register u_int m = 0xffffffff; if (*addr) while ((*addr & 0xff000000) == 0) *addr <<= 8, m <<= 8; return m; } struct block * Argusgen_tcode(name, q) int name; struct qual q; { int proto = q.proto; int dir = q.dir; struct block *b0 = NULL, *b1 = NULL; switch (name) { case Q_RETRANS: { switch (dir) { case Q_SRC: b1 = Argusgen_proto_abbrev(Q_SRCRETRANS); break; case Q_DST: b1 = Argusgen_proto_abbrev(Q_DSTRETRANS); break; case Q_AND: b0 = Argusgen_proto_abbrev(Q_SRCRETRANS); b1 = Argusgen_proto_abbrev(Q_DSTRETRANS); Argusgen_and(b0, b1); break; default: case Q_OR: b1 = Argusgen_proto_abbrev(Q_RETRANS); break; } break; } case Q_FRAG: { switch (dir) { case Q_SRC: b1 = Argusgen_mcmp(56, BPF_H, (u_int) proto, proto); break; case Q_DST: b1 = Argusgen_mcmp(58, BPF_H, (u_int) proto, proto); break; case Q_AND: b0 = Argusgen_mcmp(56, BPF_H, (u_int) proto, proto); b1 = Argusgen_mcmp(58, BPF_H, (u_int) proto, proto); Argusgen_or(b0, b1); break; default: case Q_OR: b0 = Argusgen_mcmp(56, BPF_H, (u_int) proto, proto); b1 = Argusgen_mcmp(58, BPF_H, (u_int) proto, proto); Argusgen_or(b0, b1); break; } b0 = Argusgen_tcode(Q_FRAG_ONLY, q); Argusgen_or(b0, b1); break; } case Q_FRAG_ONLY: { b1 = Argusgen_cmp(49, BPF_B, (u_int) ARGUS_FRAG_FLOWTAG); break; } case Q_WINSHUT: { switch (dir) { case Q_SRC: b1 = Argusgen_tcpstatustype(ARGUS_SRC_WINDOW_SHUT); break; case Q_DST: b1 = Argusgen_tcpstatustype(ARGUS_DST_WINDOW_SHUT); break; case Q_AND: b0 = Argusgen_tcpstatustype(ARGUS_SRC_WINDOW_SHUT); b1 = Argusgen_tcpstatustype(ARGUS_DST_WINDOW_SHUT); Argusgen_and(b0, b1); break; default: case Q_OR: b0 = Argusgen_tcpstatustype(ARGUS_SRC_WINDOW_SHUT); b1 = Argusgen_tcpstatustype(ARGUS_DST_WINDOW_SHUT); Argusgen_or(b0, b1); break; } break; } case Q_ECN: { switch (dir) { case Q_SRC: b1 = Argusgen_tcpstatustype(ARGUS_SRC_CONGESTED); break; case Q_DST: b1 = Argusgen_tcpstatustype(ARGUS_DST_CONGESTED); break; case Q_AND: b0 = Argusgen_tcpstatustype(ARGUS_SRC_CONGESTED); b1 = Argusgen_tcpstatustype(ARGUS_DST_CONGESTED); Argusgen_and(b0, b1); break; default: case Q_OR: b0 = Argusgen_tcpstatustype(ARGUS_SRC_CONGESTED); b1 = Argusgen_tcpstatustype(ARGUS_DST_CONGESTED); Argusgen_or(b0, b1); break; } break; } case Q_RESET: { switch (dir) { case Q_SRC: b1 = Argusgen_tcpstatustype(ARGUS_SRC_RESET); break; case Q_DST: b1 = Argusgen_tcpstatustype(ARGUS_DST_RESET); break; case Q_AND: b0 = Argusgen_tcpstatustype(ARGUS_SRC_RESET); b1 = Argusgen_tcpstatustype(ARGUS_DST_RESET); Argusgen_and(b0, b1); break; default: case Q_OR: b0 = Argusgen_tcpstatustype(ARGUS_SRC_RESET); b1 = Argusgen_tcpstatustype(ARGUS_DST_RESET); Argusgen_or(b0, b1); break; } break; } } return b1; } struct block * Argusgen_scode(name, q) char *name; struct qual q; { int proto = q.proto; int dir = q.dir; u_char *eaddr; u_int mask, addr; u_int **alist; struct block *b, *tmp; int port, real_proto; switch (q.addr) { case Q_NET: addr = argus_nametonetaddr(name); if (addr == 0) ArgusLog(LOG_ERR,"unknown network '%s'", name); mask = net_mask(&addr); return Argusgen_host(addr, mask, proto, dir); case Q_DEFAULT: case Q_HOST: if (proto == Q_LINK) { eaddr = argus_ether_hostton(name); if (eaddr == NULL) ArgusLog(LOG_ERR,"unknown ether host '%s'", name); return Argusgen_ehostop(eaddr, dir); } else if (proto == Q_DECNET) { unsigned short dn_addr = __argus_nametodnaddr(name); /* * I don't think DECNET hosts can be multihomed, so * there is no need to build up a list of addresses */ return (Argusgen_host(dn_addr, 0, proto, dir)); } else { alist = argus_nametoaddr(name); if (alist == NULL || *alist == NULL) ArgusLog(LOG_ERR,"unknown host '%s'", name); b = Argusgen_host(**alist++, 0xffffffffL, proto, dir); while (*alist) { tmp = Argusgen_host(**alist++, 0xffffffffL, proto, dir); Argusgen_or(b, tmp); b = tmp; } return b; } case Q_PORT: if (proto != Q_DEFAULT && proto != Q_UDP && proto != Q_TCP && proto != Q_RTP) ArgusLog(LOG_ERR,"illegal qualifier of 'port'"); if (argus_nametoport(name, &port, &real_proto) == 0) ArgusLog(LOG_ERR,"unknown port '%s'", name); if ((proto == Q_UDP) || (proto == Q_RTP)) { if (real_proto == IPPROTO_TCP) ArgusLog(LOG_ERR,"port '%s' is tcp", name); else /* override PROTO_UNDEF */ real_proto = IPPROTO_UDP; } if (proto == Q_TCP) { if (real_proto == IPPROTO_UDP) ArgusLog(LOG_ERR,"port '%s' is udp", name); else /* override PROTO_UNDEF */ real_proto = IPPROTO_TCP; } return Argusgen_port(port, real_proto, dir); case Q_GATEWAY: eaddr = argus_ether_hostton(name); if (eaddr == NULL) ArgusLog(LOG_ERR,"unknown ether host: %s", name); alist = argus_nametoaddr(name); if (alist == NULL || *alist == NULL) ArgusLog(LOG_ERR,"unknown host '%s'", name); return Argusgen_gateway(eaddr, alist, proto, dir); case Q_PROTO: real_proto = lookup_proto(name, proto); if (real_proto >= 0) return Argusgen_proto(real_proto, proto, dir); else ArgusLog(LOG_ERR,"unknown protocol: %s", name); case Q_UNDEF: syntax(); /* NOTREACHED */ } abort(); /* NOTREACHED */ } struct block * Argusgen_mcode(s1, s2, masklen, q) char *s1, *s2; int masklen; struct qual q; { struct block *b0 = NULL; int nlen, mlen; u_int n, m; nlen = __argus_atoin(s1, &n); /* Promote short ipaddr */ n <<= 32 - nlen; if (s2 != NULL) { mlen = __argus_atoin(s2, &m); /* Promote short ipaddr */ m <<= 32 - mlen; } else { /* Convert mask len to mask */ if (masklen > 32) ArgusLog(LOG_ERR,"mask length must be <= 32"); m = 0xffffffff << (32 - masklen); } switch (q.addr) { case Q_NET: b0 = Argusgen_host(n, m, q.proto, q.dir); break; default: ArgusLog(LOG_ERR,"Mask syntax for networks only"); /* NOTREACHED */ } return b0; } struct block * Argusgen_ncode(char *s, u_int v, struct qual q) { u_int mask; int proto = q.proto; int dir = q.dir; int vlen; if (s == NULL) vlen = 32; else vlen = __argus_atoin(s, &v); switch (q.addr) { case Q_DEFAULT: case Q_HOST: case Q_NET: if (proto == Q_LINK) { ArgusLog(LOG_ERR,"illegal link layer address"); } else { /* mask = net_mask(&v); */ mask = 0xffffffff; if ((s == NULL) && (q.addr == Q_NET)) { /* Promote short net number */ while (v && (v & 0xff000000) == 0) { v <<= 8; mask <<= 8; } } else { /* Promote short ipaddr */ v <<= 32 - vlen; mask <<= 32 - vlen; } return Argusgen_host(v, mask, proto, dir); } case Q_PORT: if (proto == Q_UDP) proto = IPPROTO_UDP; else if (proto == Q_RTP) proto = IPPROTO_RTP; else if (proto == Q_TCP) proto = IPPROTO_TCP; else if (proto == Q_DEFAULT) proto = PROTO_UNDEF; else ArgusLog(LOG_ERR,"illegal qualifier of 'port'"); return Argusgen_port((int)v, proto, dir); case Q_GATEWAY: ArgusLog(LOG_ERR,"'gateway' requires a name"); /* NOTREACHED */ case Q_PROTO: return Argusgen_proto((int)v, proto, dir); case Q_TTL: return Argusgen_ttl((int)v, dir); case Q_TOS: return Argusgen_tos((int)v, dir); case Q_UNDEF: syntax(); /* NOTREACHED */ default: abort(); /* NOTREACHED */ } /* NOTREACHED */ } struct block * Argusgen_ecode(eaddr, q) u_char *eaddr; struct qual q; { struct block *b0 = NULL; if ((q.addr == Q_HOST || q.addr == Q_DEFAULT) && q.proto == Q_LINK) b0 = Argusgen_ehostop(eaddr, (int)q.dir); else ArgusLog(LOG_ERR,"ethernet address used in non-ether expression"); return b0; } void Argussappend(s0, s1) struct slist *s0, *s1; { /* * This is definitely not the best way to do this, but the * lists will rarely get long. */ while (s0->next) s0 = s0->next; s0->next = s1; } static struct slist * xfer_to_x(a) struct arth *a; { struct slist *s; s = new_stmt(BPF_LDX|BPF_MEM); s->s.k = a->regno; return s; } static struct slist * xfer_to_a(a) struct arth *a; { struct slist *s; s = new_stmt(BPF_LD|BPF_MEM); s->s.k = a->regno; return s; } struct arth * Argusgen_load(proto, index, size) int proto; struct arth *index; int size; { struct slist *s, *tmp; struct block *b; int regno = alloc_reg(); free_reg(index->regno); switch (size) { default: ArgusLog(LOG_ERR,"data size must be 1, 2, or 4"); case 1: size = BPF_B; break; case 2: size = BPF_H; break; case 4: size = BPF_W; break; } switch (proto) { default: ArgusLog(LOG_ERR,"unsupported index operation"); case Q_LINK: s = xfer_to_x(index); tmp = new_stmt(BPF_LD|BPF_IND|size); Argussappend(s, tmp); Argussappend(index->s, s); break; case Q_IP: case Q_ARP: case Q_RARP: s = xfer_to_x(index); tmp = new_stmt(BPF_LD|BPF_IND|size); tmp->s.k = off_nl; Argussappend(s, tmp); Argussappend(index->s, s); b = Argusgen_proto_abbrev(proto); if (index->b) Argusgen_and(index->b, b); index->b = b; break; case Q_TCP: case Q_RTP: case Q_UDP: case Q_ICMP: case Q_IGMP: case Q_IGRP: s = new_stmt(BPF_LDX|BPF_MSH|BPF_B); s->s.k = off_nl; Argussappend(s, xfer_to_a(index)); Argussappend(s, new_stmt(BPF_ALU|BPF_ADD|BPF_X)); Argussappend(s, new_stmt(BPF_MISC|BPF_TAX)); Argussappend(s, tmp = new_stmt(BPF_LD|BPF_IND|size)); tmp->s.k = off_nl; Argussappend(index->s, s); b = Argusgen_proto_abbrev(proto); if (index->b) Argusgen_and(index->b, b); index->b = b; break; } index->regno = regno; s = new_stmt(BPF_ST); s->s.k = regno; Argussappend(index->s, s); return index; } struct block * Argusgen_relation(code, a0, a1, reversed) int code; struct arth *a0, *a1; int reversed; { struct slist *s0, *s1, *s2; struct block *b, *tmp; s0 = xfer_to_x(a1); s1 = xfer_to_a(a0); s2 = new_stmt(BPF_ALU|BPF_SUB|BPF_X); b = new_block(JMP(code)); if (reversed) Argusgen_not(b); Argussappend(s1, s2); Argussappend(s0, s1); Argussappend(a1->s, s0); Argussappend(a0->s, a1->s); b->stmts = a0->s; free_reg(a0->regno); free_reg(a1->regno); /* 'and' together protocol checks */ if (a0->b) { if (a1->b) { Argusgen_and(a0->b, tmp = a1->b); } else tmp = a0->b; } else tmp = a1->b; if (tmp) Argusgen_and(tmp, b); return b; } struct arth * Argusgen_loadlen() { int regno = alloc_reg(); struct arth *a = (struct arth *)newchunk(sizeof(*a)); struct slist *s; s = new_stmt(BPF_LD|BPF_LEN); s->next = new_stmt(BPF_ST); s->next->s.k = regno; a->s = s; a->regno = regno; return a; } struct arth * Argusgen_loadi(val) int val; { struct arth *a; struct slist *s; int reg; a = (struct arth *)newchunk(sizeof(*a)); reg = alloc_reg(); s = new_stmt(BPF_LD|BPF_IMM); s->s.k = val; s->next = new_stmt(BPF_ST); s->next->s.k = reg; a->s = s; a->regno = reg; return a; } struct arth * Argusgen_neg(a) struct arth *a; { struct slist *s; s = xfer_to_a(a); Argussappend(a->s, s); s = new_stmt(BPF_ALU|BPF_NEG); s->s.k = 0; Argussappend(a->s, s); s = new_stmt(BPF_ST); s->s.k = a->regno; Argussappend(a->s, s); return a; } struct arth * Argusgen_arth(code, a0, a1) int code; struct arth *a0, *a1; { struct slist *s0, *s1, *s2; s0 = xfer_to_x(a1); s1 = xfer_to_a(a0); s2 = new_stmt(BPF_ALU|BPF_X|code); Argussappend(s1, s2); Argussappend(s0, s1); Argussappend(a1->s, s0); Argussappend(a0->s, a1->s); free_reg(a1->regno); s0 = new_stmt(BPF_ST); a0->regno = s0->s.k = alloc_reg(); Argussappend(a0->s, s0); return a0; } /* * Here we handle simple allocation of the scratch registers. * If too many registers are alloc'd, the allocator punts. */ static int regused[BPF_MEMWORDS]; static int curreg; /* * Return the next free register. */ static int alloc_reg() { int retn = -1; int n = BPF_MEMWORDS; while (--n >= 0) { if (regused[curreg]) curreg = (curreg + 1) % BPF_MEMWORDS; else { regused[curreg] = 1; retn = curreg; break; } } if (retn == -1) ArgusLog(LOG_ERR,"too many registers needed to evaluate expression"); return (retn); } /* * Return a register to the table so it can * be used later. */ static void free_reg(n) int n; { regused[n] = 0; } static struct block * Argusgen_len(jmp, n) int jmp, n; { struct slist *s; struct block *b; s = new_stmt(BPF_LD|BPF_LEN); s->next = new_stmt(BPF_ALU|BPF_SUB|BPF_K); s->next->s.k = n; b = new_block(JMP(jmp)); b->stmts = s; return b; } struct block * Argusgen_greater(n) int n; { fprintf (stderr, "Argusgen_greater(%d)\n", n); return Argusgen_len(BPF_JGE, n); } struct block * Argusgen_less(n) int n; { struct block *b; b = Argusgen_len(BPF_JGT, n); Argusgen_not(b); return b; } struct block * Argusgen_byteop(op, idx, val) int op, idx, val; { struct block *b; struct slist *s; switch (op) { default: abort(); case '=': return Argusgen_cmp((u_int)idx, BPF_B, (u_int)val); case '<': b = Argusgen_cmp((u_int)idx, BPF_B, (u_int)val); b->s.code = JMP(BPF_JGE); Argusgen_not(b); return b; case '>': b = Argusgen_cmp((u_int)idx, BPF_B, (u_int)val); b->s.code = JMP(BPF_JGT); return b; case '|': s = new_stmt(BPF_ALU|BPF_OR|BPF_K); break; case '&': s = new_stmt(BPF_ALU|BPF_AND|BPF_K); break; } s->s.k = val; b = new_block(JMP(BPF_JEQ)); b->stmts = s; Argusgen_not(b); return b; } struct block * Argusgen_broadcast(proto) int proto; { u_int classmask; u_int netaddr; struct block *b0, *b1; static u_char ebroadcast[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; extern u_int ArgusLocalNet; switch (proto) { case Q_DEFAULT: case Q_LINK: return Argusgen_ehostop(ebroadcast, Q_DST); case Q_IP: netaddr = ArgusLocalNet & ArgusNetMask; classmask = ipaddrtonetmask(ArgusLocalNet); b1 = Argusgen_host(netaddr, 0xffffffffL, proto, Q_OR); netaddr |= ~(~0 & ArgusNetMask); b0 = Argusgen_host(netaddr, 0xffffffffL, proto, Q_OR); Argusgen_or(b1, b0); if (classmask != ArgusNetMask) { netaddr = ArgusLocalNet & classmask; b1 = Argusgen_host(netaddr, 0xffffffffL, proto, Q_OR); Argusgen_or(b1, b0); } b1 = Argusgen_host( ~0, 0xffffffffL, proto, Q_OR); Argusgen_or(b1, b0); return b0; } ArgusLog(LOG_ERR,"only ether/ip broadcast filters supported"); return NULL; } struct block * Argusgen_multicast(proto) int proto; { register struct block *b0, *b1; register struct slist *s; switch (proto) { case Q_DEFAULT: case Q_LINK: s = new_stmt(BPF_LD|BPF_B|BPF_ABS); s->s.k = 88; b0 = new_block(JMP(BPF_JSET)); b0->s.k = 1; b0->stmts = s; return b0; case Q_IP: b1 = Argusgen_cmp(40, BPF_B, (u_int) 224); b1->s.code = JMP(BPF_JGE); b0 = Argusgen_cmp(44, BPF_B, (u_int) 224); b0->s.code = JMP(BPF_JGE); Argusgen_or(b0, b1); return b1; } return NULL; } /* * generate command for inbound/outbound. It's here so we can * make it link-type specific. 'dir' = 0 implies "inbound", * = 1 implies "outbound". */ struct block * Argusgen_inbound(dir) int dir; { register struct block *b0; b0 = Argusgen_relation(BPF_JEQ, Argusgen_load(Q_LINK, Argusgen_loadi(0), 1), Argusgen_loadi(0), dir); return (b0); }