diff options
author | luigi <luigi@FreeBSD.org> | 2002-06-27 23:02:18 +0000 |
---|---|---|
committer | luigi <luigi@FreeBSD.org> | 2002-06-27 23:02:18 +0000 |
commit | a9ab854862b9e8f268eb8bbbac00742895dbb2c3 (patch) | |
tree | 53922ec5b28d758363c3c72bc71078ef26229c57 /sbin | |
parent | 63fdb8e6ba59d01bbbb957a13d305d04a276e889 (diff) | |
download | FreeBSD-src-a9ab854862b9e8f268eb8bbbac00742895dbb2c3.zip FreeBSD-src-a9ab854862b9e8f268eb8bbbac00742895dbb2c3.tar.gz |
The new ipfw code.
This code makes use of variable-size kernel representation of rules
(exactly the same concept of BPF instructions, as used in the BSDI's
firewall), which makes firewall operation a lot faster, and the
code more readable and easier to extend and debug.
The interface with the rest of the system is unchanged, as witnessed
by this commit. The only extra kernel files that I am touching
are if_fw.h and ip_dummynet.c, which is quite tied to ipfw. In
userland I only had to touch those programs which manipulate the
internal representation of firewall rules).
The code is almost entirely new (and I believe I have written the
vast majority of those sections which were taken from the former
ip_fw.c), so rather than modifying the old ip_fw.c I decided to
create a new file, sys/netinet/ip_fw2.c . Same for the user
interface, which is in sbin/ipfw/ipfw2.c (it still compiles to
/sbin/ipfw). The old files are still there, and will be removed
in due time.
I have not renamed the header file because it would have required
touching a one-line change to a number of kernel files.
In terms of user interface, the new "ipfw" is supposed to accepts
the old syntax for ipfw rules (and produce the same output with
"ipfw show". Only a couple of the old options (out of some 30 of
them) has not been implemented, but they will be soon.
On the other hand, the new code has some very powerful extensions.
First, you can put "or" connectives between match fields (and soon
also between options), and write things like
ipfw add allow ip from { 1.2.3.4/27 or 5.6.7.8/30 } 10-23,25,1024-3000 to any
This should make rulesets slightly more compact (and lines longer!),
by condensing 2 or more of the old rules into single ones.
Also, as an example of how easy the rules can be extended, I have
implemented an 'address set' match pattern, where you can specify
an IP address in a format like this:
10.20.30.0/26{18,44,33,22,9}
which will match the set of hosts listed in braces belonging to the
subnet 10.20.30.0/26 . The match is done using a bitmap, so it is
essentially a constant time operation requiring a handful of CPU
instructions (and a very small amount of memmory -- for a full /24
subnet, the instruction only consumes 40 bytes).
Again, in this commit I have focused on functionality and tried
to minimize changes to the other parts of the system. Some performance
improvement can be achieved with minor changes to the interface of
ip_fw_chk_t. This will be done later when this code is settled.
The code is meant to compile unmodified on RELENG_4 (once the
PACKET_TAG_* changes have been merged), for this reason
you will see #ifdef __FreeBSD_version in a couple of places.
This should minimize errors when (hopefully soon) it will be time
to do the MFC.
Diffstat (limited to 'sbin')
-rw-r--r-- | sbin/ipfw/Makefile | 3 | ||||
-rw-r--r-- | sbin/ipfw/ipfw2.c | 3178 |
2 files changed, 3180 insertions, 1 deletions
diff --git a/sbin/ipfw/Makefile b/sbin/ipfw/Makefile index 241d304..e49fe99 100644 --- a/sbin/ipfw/Makefile +++ b/sbin/ipfw/Makefile @@ -1,7 +1,8 @@ # $FreeBSD$ PROG= ipfw -WARNS= 0 +SRCS= ipfw2.c +#WARNS= 0 MAN= ipfw.8 .include <bsd.prog.mk> diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c new file mode 100644 index 0000000..5cf462e --- /dev/null +++ b/sbin/ipfw/ipfw2.c @@ -0,0 +1,3178 @@ +/* + * Copyright (c) 2002 Luigi Rizzo + * Copyright (c) 1996 Alex Nash, Paul Traina, Poul-Henning Kamp + * Copyright (c) 1994 Ugen J.S.Antsilevich + * + * Idea and grammar partially left from: + * Copyright (c) 1993 Daniel Boulet + * + * Redistribution and use in source forms, with and without modification, + * are permitted provided that this entire comment appears intact. + * + * Redistribution in binary form may occur without any restrictions. + * Obviously, it would be nice if you gave credit where credit is due + * but requiring it would be too onerous. + * + * This software is provided ``AS IS'' without any warranties of any kind. + * + * NEW command line interface for IP firewall facility + * + * $FreeBSD$ + */ + +#include <sys/param.h> +#include <sys/mbuf.h> +#include <sys/socket.h> +#include <sys/sockio.h> +#include <sys/sysctl.h> +#include <sys/time.h> +#include <sys/wait.h> + +#include <ctype.h> +#include <err.h> +#include <errno.h> +#include <grp.h> +#include <limits.h> +#include <netdb.h> +#include <pwd.h> +#include <signal.h> +#include <stdio.h> +#include <stdlib.h> +#include <stdarg.h> +#include <string.h> +#include <unistd.h> +#include <sysexits.h> + +#include <net/if.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <netinet/ip_icmp.h> +#include <netinet/ip_fw.h> +#include <net/route.h> /* def. of struct route */ +#include <netinet/ip_dummynet.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> + +int s, /* main RAW socket */ + do_resolv, /* Would try to resolve all */ + do_acct, /* Show packet/byte count */ + do_time, /* Show time stamps */ + do_quiet, /* Be quiet in add and flush */ + do_force, /* Don't ask for confirmation */ + do_pipe, /* this cmd refers to a pipe */ + do_sort, /* field to sort results (0 = no) */ + do_dynamic, /* display dynamic rules */ + do_expired, /* display expired dynamic rules */ + verbose; + +#define IP_MASK_ALL 0xffffffff + +/* + * structure to hold flag names and associated values to be + * set in the appropriate masks. + * A NULL string terminates the array. + * Often, an element with 0 value contains an error string. + * + */ +struct _s_x { + char *s; + int x; +}; + +static struct _s_x f_tcpflags[] = { + { "syn", TH_SYN }, + { "fin", TH_FIN }, + { "ack", TH_ACK }, + { "psh", TH_PUSH }, + { "rst", TH_RST }, + { "urg", TH_URG }, + { "tcp flag", 0 }, + { NULL, 0 } +}; + +static struct _s_x f_tcpopts[] = { + { "mss", IP_FW_TCPOPT_MSS }, + { "maxseg", IP_FW_TCPOPT_MSS }, + { "window", IP_FW_TCPOPT_WINDOW }, + { "sack", IP_FW_TCPOPT_SACK }, + { "ts", IP_FW_TCPOPT_TS }, + { "timestamp", IP_FW_TCPOPT_TS }, + { "cc", IP_FW_TCPOPT_CC }, + { "tcp option", 0 }, + { NULL, 0 } +}; + +/* + * IP options span the range 0 to 255 so we need to remap them + * (though in fact only the low 5 bits are significant). + */ +static struct _s_x f_ipopts[] = { + { "ssrr", IP_FW_IPOPT_SSRR}, + { "lsrr", IP_FW_IPOPT_LSRR}, + { "rr", IP_FW_IPOPT_RR}, + { "ts", IP_FW_IPOPT_TS}, + { "ip option", 0 }, + { NULL, 0 } +}; + +#if 0 /* XXX not used yet */ +static struct _s_x f_iptos[] = { + { "lowdelay", IPTOS_LOWDELAY}, + { "throughput", IPTOS_THROUGHPUT}, + { "reliability", IPTOS_RELIABILITY}, + { "mincost", IPTOS_MINCOST}, + { "congestion", IPTOS_CE}, + { "ecntransport", IPTOS_ECT}, + { "ip tos option", 0}, + { NULL, 0 } +}; +#endif + +static struct _s_x limit_masks[] = { + {"all", DYN_SRC_ADDR|DYN_SRC_PORT|DYN_DST_ADDR|DYN_DST_PORT}, + {"src-addr", DYN_SRC_ADDR}, + {"src-port", DYN_SRC_PORT}, + {"dst-addr", DYN_DST_ADDR}, + {"dst-port", DYN_DST_PORT}, + {NULL, 0} +}; + +/* + * we use IPPROTO_ETHERTYPE as a fake protocol id to call the print routines + * This is only used in this code. + */ +#define IPPROTO_ETHERTYPE 0x1000 +static struct _s_x ether_types[] = { + /* + * Note, we cannot use "-:&/" in the names because they are field + * separators in the type specifications. Also, we use s = NULL as + * end-delimiter, because a type of 0 can be legal. + */ + { "ip", 0x0800 }, + { "ipv4", 0x0800 }, + { "ipv6", 0x86dd }, + { "arp", 0x0806 }, + { "rarp", 0x8035 }, + { "vlan", 0x8100 }, + { "loop", 0x9000 }, + { "trail", 0x1000 }, + { "at", 0x809b }, + { "atalk", 0x809b }, + { "aarp", 0x80f3 }, + { "pppoe_disc", 0x8863 }, + { "pppoe_sess", 0x8864 }, + { "ipx_8022", 0x00E0 }, + { "ipx_8023", 0x0000 }, + { "ipx_ii", 0x8137 }, + { "ipx_snap", 0x8137 }, + { "ipx", 0x8137 }, + { "ns", 0x0600 }, + { NULL, 0 } +}; + +static void show_usage(void); + +enum tokens { + TOK_NULL=0, + + TOK_OR, + TOK_NOT, + + TOK_ACCEPT, + TOK_COUNT, + TOK_PIPE, + TOK_QUEUE, + TOK_DIVERT, + TOK_TEE, + TOK_FORWARD, + TOK_SKIPTO, + TOK_DENY, + TOK_REJECT, + TOK_RESET, + TOK_UNREACH, + TOK_CHECKSTATE, + + TOK_UID, + TOK_GID, + TOK_IN, + TOK_LIMIT, + TOK_KEEPSTATE, + TOK_LAYER2, + TOK_OUT, + TOK_XMIT, + TOK_RECV, + TOK_VIA, + TOK_FRAG, + TOK_IPOPTS, + TOK_IPLEN, + TOK_IPID, + TOK_IPPRECEDENCE, + TOK_IPTOS, + TOK_IPTTL, + TOK_IPVER, + TOK_ESTAB, + TOK_SETUP, + TOK_TCPFLAGS, + TOK_TCPOPTS, + TOK_TCPSEQ, + TOK_TCPACK, + TOK_TCPWIN, + TOK_ICMPTYPES, + + TOK_PLR, + TOK_BUCKETS, + TOK_DSTIP, + TOK_SRCIP, + TOK_DSTPORT, + TOK_SRCPORT, + TOK_ALL, + TOK_MASK, + TOK_BW, + TOK_DELAY, + TOK_RED, + TOK_GRED, + TOK_DROPTAIL, + TOK_PROTO, + TOK_WEIGHT, +}; + +struct _s_x dummynet_params[] = { + { "plr", TOK_PLR }, + { "buckets", TOK_BUCKETS }, + { "dst-ip", TOK_DSTIP }, + { "src-ip", TOK_SRCIP }, + { "dst-port", TOK_DSTPORT }, + { "src-port", TOK_SRCPORT }, + { "proto", TOK_PROTO }, + { "weight", TOK_WEIGHT }, + { "all", TOK_ALL }, + { "mask", TOK_MASK }, + { "droptail", TOK_DROPTAIL }, + { "red", TOK_RED }, + { "gred", TOK_GRED }, + { "bw", TOK_BW }, + { "bandwidth", TOK_BW }, + { "delay", TOK_DELAY }, + { "queue", TOK_QUEUE }, + { "dummynet-params", TOK_NULL }, + { NULL, 0 } +}; + +struct _s_x rule_actions[] = { + { "accept", TOK_ACCEPT }, + { "pass", TOK_ACCEPT }, + { "allow", TOK_ACCEPT }, + { "permit", TOK_ACCEPT }, + { "count", TOK_COUNT }, + { "pipe", TOK_PIPE }, + { "queue", TOK_QUEUE }, + { "divert", TOK_DIVERT }, + { "tee", TOK_TEE }, + { "fwd", TOK_FORWARD }, + { "forward", TOK_FORWARD }, + { "skipto", TOK_SKIPTO }, + { "deny", TOK_DENY }, + { "drop", TOK_DENY }, + { "reject", TOK_REJECT }, + { "reset", TOK_RESET }, + { "check-state", TOK_CHECKSTATE }, + { NULL, TOK_NULL }, + { NULL, 0 } +}; + +struct _s_x rule_options[] = { + { "uid", TOK_UID }, + { "gid", TOK_GID }, + { "in", TOK_IN }, + { "limit", TOK_LIMIT }, + { "keep-state", TOK_KEEPSTATE }, + { "bridged", TOK_LAYER2 }, + { "layer2", TOK_LAYER2 }, + { "out", TOK_OUT }, + { "xmit", TOK_XMIT }, + { "recv", TOK_RECV }, + { "via", TOK_VIA }, + { "fragment", TOK_FRAG }, + { "frag", TOK_FRAG }, + { "ipoptions", TOK_IPOPTS }, + { "ipopts", TOK_IPOPTS }, + { "iplen", TOK_IPLEN }, + { "ipid", TOK_IPID }, + { "ipprecedence", TOK_IPPRECEDENCE }, + { "iptos", TOK_IPTOS }, + { "ipttl", TOK_IPTTL }, + { "ipversion", TOK_IPVER }, + { "ipver", TOK_IPVER }, + { "estab", TOK_ESTAB }, + { "established", TOK_ESTAB }, + { "setup", TOK_SETUP }, + { "tcpflags", TOK_TCPFLAGS }, + { "tcpflgs", TOK_TCPFLAGS }, + { "tcpoptions", TOK_TCPOPTS }, + { "tcpopts", TOK_TCPOPTS }, + { "tcpseq", TOK_TCPSEQ }, + { "tcpack", TOK_TCPACK }, + { "tcpwin", TOK_TCPWIN }, + { "icmptypes", TOK_ICMPTYPES }, + + { "not", TOK_NOT }, /* pseudo option */ + { "!", /* escape ? */ TOK_NOT }, /* pseudo option */ + { "or", TOK_OR }, /* pseudo option */ + { "|", /* escape */ TOK_OR }, /* pseudo option */ + { NULL, TOK_NULL }, + { NULL, 0 } +}; + +/** + * match_token takes a table and a string, returns the value associated + * with the string (0 meaning an error in most cases) + */ +static int +match_token(struct _s_x *table, char *string) +{ + struct _s_x *pt; + int i = strlen(string); + + for (pt = table ; i && pt->s != NULL ; pt++) + if (strlen(pt->s) == i && !bcmp(string, pt->s, i)) + return pt->x; + return -1; +}; + +static char * +match_value(struct _s_x *p, u_int32_t value) +{ + for (; p->s != NULL; p++) + if (p->x == value) + return p->s; + return NULL; +} + +/* + * prints one port, symbolic or numeric + */ +static void +print_port(int proto, u_int16_t port) +{ + + if (proto == IPPROTO_ETHERTYPE) { + char *s; + + if (do_resolv && (s = match_value(ether_types, port)) ) + printf("%s", s); + else + printf("0x%04x", port); + } else { + struct servent *se = NULL; + if (do_resolv) { + struct protoent *pe = getprotobynumber(proto); + + se = getservbyport(htons(port), pe ? pe->p_name : NULL); + } + if (se) + printf("%s", se->s_name); + else + printf("%d", port); + } +} + +/* + * print the values in a list of ports + * XXX todo: add support for mask. + */ +static void +print_newports(ipfw_insn_u16 *cmd, int proto) +{ + u_int16_t *p = cmd->ports; + int i; + char *sep= " "; + + if (cmd->o.len & F_NOT) + printf(" not"); + for (i = F_LEN((ipfw_insn *)cmd) - 1; i > 0; i--, p += 2) { + printf(sep); + print_port(proto, p[0]); + if (p[0] != p[1]) { + printf("-"); + print_port(proto, p[1]); + } + sep = ","; + } +} + +/* + * Like strtol, but also translates service names into port numbers + * for some protocols. + * In particular: + * proto == -1 disables the protocol check; + * proto == IPPROTO_ETHERTYPE looks up an internal table + * proto == <some value in /etc/protocols> matches the values there. + */ +static int +strtoport(char *s, char **end, int base, int proto) +{ + char *s1, sep; + int i; + + if ( *s == '\0') + goto none; + + if (isdigit(*s)) + return strtol(s, end, base); + + /* + * find separator and replace with a '\0' + */ + for (s1 = s; *s1 && isalnum(*s1) ; s1++) + ; + sep = *s1; + *s1 = '\0'; + + if (proto == IPPROTO_ETHERTYPE) { + i = match_token(ether_types, s); + *s1 = sep; + if (i == -1) { /* not found */ + *end = s; + return 0; + } else { + *end = s1; + return i; + } + } else { + struct protoent *pe = NULL; + struct servent *se; + + if (proto != 0) + pe = getprotobynumber(proto); + setservent(1); + se = getservbyname(s, pe ? pe->p_name : NULL); + *s1 = sep; + if (se != NULL) { + *end = s1; + return ntohs(se->s_port); + } + } +none: + *end = s; + return 0; +} + +/* + * fill the body of the command with the list of port ranges. + * At the moment it only understands numeric ranges. + */ +static int +fill_newports(ipfw_insn_u16 *cmd, char *av, int proto) +{ + u_int16_t *p = cmd->ports; + int i = 0; + + for (; *av ; i++, p +=2 ) { + u_int16_t a, b; + char *s; + + a = strtoport(av, &s, 0, proto); + if (s == av) /* no parameter */ + break; + if (*s == '-') { /* a range */ + av = s+1; + b = strtoport(av, &s, 0, proto); + if (s == av) /* no parameter */ + break; + p[0] = a; + p[1] = b; + } else if (*s == ',' || *s == '\0' ) { + p[0] = p[1] = a; + } else /* invalid separator */ + break; + av = s+1; + } + if (i > 0) { + if (i+1 > F_LEN_MASK) + errx(EX_DATAERR, "too many port range\n"); + cmd->o.len |= i+1; /* leave F_NOT and F_OR untouched */ + } + return i; +} + +#if 0 /* XXX not used yet */ +static struct _s_x icmpcodes[] = { + { "net", ICMP_UNREACH_NET }, + { "host", ICMP_UNREACH_HOST }, + { "protocol", ICMP_UNREACH_PROTOCOL }, + { "port", ICMP_UNREACH_PORT }, + { "needfrag", ICMP_UNREACH_NEEDFRAG }, + { "srcfail", ICMP_UNREACH_SRCFAIL }, + { "net-unknown", ICMP_UNREACH_NET_UNKNOWN }, + { "host-unknown", ICMP_UNREACH_HOST_UNKNOWN }, + { "isolated", ICMP_UNREACH_ISOLATED }, + { "net-prohib", ICMP_UNREACH_NET_PROHIB }, + { "host-prohib", ICMP_UNREACH_HOST_PROHIB }, + { "tosnet", ICMP_UNREACH_TOSNET }, + { "toshost", ICMP_UNREACH_TOSHOST }, + { "filter-prohib", ICMP_UNREACH_FILTER_PROHIB }, + { "host-precedence", ICMP_UNREACH_HOST_PRECEDENCE }, + { "precedence-cutoff", ICMP_UNREACH_PRECEDENCE_CUTOFF }, + { NULL, 0 } +}; + +static void +fill_reject_code(u_short *codep, char *str) +{ + int val; + char *s; + + val = strtoul(str, &s, 0); + if (s == str || *s != '\0' || val >= 0x100) + val = match_token(icmpcodes, str); + if (val <= 0) + errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str); + *codep = val; + return; +} + +static void +print_reject_code(u_int32_t code) +{ + char *s = match_value(icmpcodes, code); + + if (s != NULL) + printf("%s", s); + else + printf("%u", code); +} +#endif /* XXX not used yet */ + +/* + * Returns the number of bits set (from left) in a contiguous bitmask, + * or -1 if the mask is not contiguous. + * XXX this needs a proper fix. + * This effectively works on masks in big-endian (network) format. + * when compiled on little endian architectures. + * + * First bit is bit 7 of the first byte -- note, for MAC addresses, + * the first bit on the wire is bit 0 of the first byte. + * len is the max length in bits. + */ +static int +contigmask(u_char *p, int len) +{ + int i, n; + for (i=0; i<len ; i++) + if ( (p[i/8] & (1 << (7 - (i%8)))) == 0) /* first bit unset */ + break; + for (n=i+1; n < len; n++) + if ( (p[n/8] & (1 << (7 - (n%8)))) != 0) + return -1; /* mask not contiguous */ + return i; +} + +/* + * print flags set/clear in the two bitmasks passed as parameters. + * There is a specialized check for f_tcpflags. + */ +static void +print_flags(char *name, ipfw_insn *cmd, struct _s_x *list) +{ + char *comma=""; + int i; + u_char set = cmd->arg1 & 0xff; + u_char clear = (cmd->arg1 >> 8) & 0xff; + + if (list == f_tcpflags && set == TH_SYN && clear == TH_ACK) { + printf(" setup"); + return; + } + + printf(" %s ", name); + for (i=0; list[i].x != 0; i++) { + if (set & list[i].x) { + set &= ~list[i].x; + printf("%s%s", comma, list[i].s); + comma = ","; + } + if (clear & list[i].x) { + clear &= ~list[i].x; + printf("%s!%s", comma, list[i].s); + comma = ","; + } + } +} + +/* + * Print the ip address contained in a command. + */ +static void +print_ip(ipfw_insn_ip *cmd) +{ + struct hostent *he = NULL; + int mb; + + printf("%s ", cmd->o.len & F_NOT ? " not": ""); + + if (cmd->o.opcode == O_IP_SRC_ME || cmd->o.opcode == O_IP_DST_ME) { + printf("me"); + return; + } + if (cmd->o.opcode == O_IP_SRC_SET || cmd->o.opcode == O_IP_DST_SET) { + u_int32_t x, *d; + int i; + char comma = '{'; + + x = cmd->o.arg1 - 1; + x = htonl( ~x ); + cmd->addr.s_addr = htonl(cmd->addr.s_addr); + printf("%s/%d", inet_ntoa(cmd->addr), + contigmask((u_char *)&x, 32)); + x = cmd->addr.s_addr = htonl(cmd->addr.s_addr); + x &= 0xff; /* base */ + d = (u_int32_t *)&(cmd->mask); + for (i=0; i < cmd->o.arg1; i++) + if (d[ i/32] & (1<<(i & 31))) { + printf("%c%d", comma, i+x); + comma = ','; + } + printf("}"); + return; + } + if (cmd->o.opcode == O_IP_SRC || cmd->o.opcode == O_IP_DST) + mb = 32; + else + mb = contigmask((u_char *)&(cmd->mask.s_addr), 32); + if (mb == 32 && do_resolv) + he = gethostbyaddr((char *)&(cmd->addr.s_addr), + sizeof(u_long), AF_INET); + if (he != NULL) /* resolved to name */ + printf("%s", he->h_name); + else if (mb == 0) /* any */ + printf("any"); + else { /* numeric IP followed by some kind of mask */ + printf("%s", inet_ntoa(cmd->addr)); + if (mb < 0) + printf(":%s", inet_ntoa(cmd->mask)); + else if (mb < 32) + printf("/%d", mb); + } +} + +/* + * prints a MAC address/mask pair + */ +static void +print_mac(u_char *addr, u_char *mask) +{ + int l = contigmask(mask, 48); + + if (l == 0) + printf(" any"); + else { + printf(" %02x:%02x:%02x:%02x:%02x:%02x", + addr[0], addr[1], addr[2], addr[3], addr[4], addr[5]); + if (l == -1) + printf("&%02x:%02x:%02x:%02x:%02x:%02x", + mask[0], mask[1], mask[2], + mask[3], mask[4], mask[5]); + else if (l < 48) + printf("/%d", l); + } +} + + +/* + * show_ipfw() prints the body of an ipfw rule. + * Because the standard rule has at least proto src_ip dst_ip, we use + * a helper function to produce these entries if not provided explicitly. + */ +#define HAVE_PROTO 1 +#define HAVE_SRCIP 2 +#define HAVE_DSTIP 4 +#define HAVE_MAC 8 + +static void +show_prerequisites(int *flags, int want) +{ + if ( !(*flags & HAVE_PROTO) && (want & HAVE_PROTO)) + printf(" ip"); + if ( !(*flags & HAVE_SRCIP) && (want & HAVE_SRCIP)) + printf(" from any"); + if ( !(*flags & HAVE_DSTIP) && (want & HAVE_DSTIP)) + printf(" to any"); + *flags |= want; +} + +static void +show_ipfw(struct ip_fw *rule) +{ + int l; + ipfw_insn *cmd; + int proto = 0; /* default */ + int flags = 0; /* prerequisites */ + ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */ + int or_block = 0; /* we are in an or block */ + + printf("%05u ", rule->rulenum); + + if (do_acct) + printf("%10qu %10qu ", rule->pcnt, rule->bcnt); + + if (do_time) { + if (rule->timestamp) { + char timestr[30]; + time_t t = _long_to_time(rule->timestamp); + + strcpy(timestr, ctime(&t)); + *strchr(timestr, '\n') = '\0'; + printf("%s ", timestr); + } else { + printf(" "); + } + } + + /* + * first print actions + */ + for (l = rule->cmd_len - rule->act_ofs, cmd = ACTION_PTR(rule); + l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) { + switch(cmd->opcode) { + case O_CHECK_STATE: + printf("check-state"); + /* avoid printing anything else */ + flags = HAVE_PROTO|HAVE_SRCIP|HAVE_DSTIP; + break; + + case O_PROB: + { + ipfw_insn_u32 *p = (ipfw_insn_u32 *)cmd; + double d = 1.0 * p->d[0]; + + d = 1 - (d / 0x7fffffff); + printf("prob %f ", d); + } + break; + + case O_ACCEPT: + printf("allow"); + break; + + case O_COUNT: + printf("count"); + break; + + case O_DENY: + printf("deny"); + break; + + case O_SKIPTO: + printf("skipto %u", cmd->arg1); + break; + + case O_PIPE: + printf("pipe %u", cmd->arg1); + break; + + case O_QUEUE: + printf("queue %u", cmd->arg1); + break; + + case O_DIVERT: + printf("divert %u", cmd->arg1); + break; + + case O_TEE: + printf("tee %u", cmd->arg1); + break; + + case O_FORWARD_IP: + { + ipfw_insn_sa *s = (ipfw_insn_sa *)cmd; + + printf("fwd %s", inet_ntoa(s->sa.sin_addr)); + if (s->sa.sin_port) + printf(",%d", ntohs(s->sa.sin_port)); + } + break; + + case O_LOG: /* O_LOG is printed last */ + logptr = (ipfw_insn_log *)cmd; + break; + + default: + printf("** unrecognized action %d len %d", + cmd->opcode, cmd->len); + } + } + if (logptr) { + if (logptr->max_log > 0) + printf(" log logamount %d ", logptr->max_log); + else + printf(" log "); + } + /* + * then print the body + */ + for (l = rule->act_ofs, cmd = rule->cmd ; + l > 0 ; l -= F_LEN(cmd) , cmd += F_LEN(cmd)) { + ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd; /* useful alias */ + + switch(cmd->opcode) { + case O_PROBE_STATE: + break; /* no need to print anything here */ + + case O_MACADDR2: { + ipfw_insn_mac *m = (ipfw_insn_mac *)cmd; + if ( (flags & HAVE_MAC) == 0) + printf(" MAC"); + flags |= HAVE_MAC; + if (cmd->len & F_NOT) + printf(" not"); + print_mac( m->addr, m->mask); + print_mac( m->addr + 6, m->mask + 6); + } + break; + + case O_MAC_TYPE: + print_newports((ipfw_insn_u16 *)cmd, IPPROTO_ETHERTYPE); + break; + + case O_IP_SRC: + case O_IP_SRC_MASK: + case O_IP_SRC_ME: + case O_IP_SRC_SET: + show_prerequisites(&flags, HAVE_PROTO); + if (!(flags & HAVE_SRCIP)) + printf(" from"); + if ((cmd->len & F_OR) && !or_block) + printf(" {"); + print_ip((ipfw_insn_ip *)cmd); + flags |= HAVE_SRCIP; + break; + + case O_IP_DST: + case O_IP_DST_MASK: + case O_IP_DST_ME: + case O_IP_DST_SET: + show_prerequisites(&flags, HAVE_PROTO|HAVE_SRCIP); + if (!(flags & HAVE_DSTIP)) + printf(" to"); + if ((cmd->len & F_OR) && !or_block) + printf(" {"); + print_ip((ipfw_insn_ip *)cmd); + flags |= HAVE_DSTIP; + break; + + case O_IP_DSTPORT: + show_prerequisites(&flags, + HAVE_PROTO|HAVE_SRCIP|HAVE_DSTIP); + case O_IP_SRCPORT: + show_prerequisites(&flags, HAVE_PROTO|HAVE_SRCIP); + print_newports((ipfw_insn_u16 *)cmd, proto); + break; + + case O_PROTO: { + struct protoent *pe; + + if ((cmd->len & F_OR) && !or_block) + printf(" {"); + if (cmd->len & F_NOT) + printf(" not"); + proto = cmd->arg1; + pe = getprotobynumber(cmd->arg1); + if (pe) + printf(" %s", pe->p_name); + else + printf(" %u", cmd->arg1); + } + flags |= HAVE_PROTO; + break; + + default: /*options ... */ + show_prerequisites(&flags, + HAVE_PROTO|HAVE_SRCIP|HAVE_DSTIP); + if ((cmd->len & F_OR) && !or_block) + printf(" {"); + if (cmd->len & F_NOT && cmd->opcode != O_IN) + printf(" not"); + switch(cmd->opcode) { + case O_FRAG: + printf(" frag"); + break; + + case O_IN: + printf(cmd->len & F_NOT ? " out" : " in"); + break; + + case O_LAYER2: + printf(" layer2"); + break; + case O_XMIT: + case O_RECV: + case O_VIA: { + char *s; + ipfw_insn_if *cmdif = (ipfw_insn_if *)cmd; + + if (cmd->opcode == O_XMIT) + s = "xmit"; + else if (cmd->opcode == O_RECV) + s = "recv"; + else if (cmd->opcode == O_VIA) + s = "via"; + if (cmdif->name[0] == '\0') + printf(" %s %s", s, inet_ntoa(cmdif->p.ip)); + else if (cmdif->p.unit == -1) + printf(" %s %s*", s, cmdif->name); + else + printf(" %s %s%d", s, cmdif->name, cmdif->p.unit); + } + break; + + case O_IPID: + printf(" ipid %u", cmd->arg1 ); + break; + + case O_IPTTL: + printf(" ipttl %u", cmd->arg1 ); + break; + + case O_IPVER: + printf(" ipver %u", cmd->arg1 ); + break; + + case O_IPLEN: + printf(" iplen %u", cmd->arg1 ); + break; + + case O_IPOPTS: + print_flags("ipoptions", cmd, f_ipopts); + break; + + case O_ESTAB: + printf(" established"); + break; + + case O_TCPFLAGS: + print_flags("tcpflags", cmd, f_tcpflags); + break; + + case O_TCPOPTS: + print_flags("tcpoptions", cmd, f_tcpopts); + break; + + case O_TCPWIN: + printf(" tcpwin %d", ntohs(cmd->arg1)); + break; + + case O_TCPACK: + printf(" tcpack %d", ntohl(cmd32->d[0])); + break; + + case O_TCPSEQ: + printf(" tcpseq %d", ntohl(cmd32->d[0])); + break; + + case O_UID: + { + struct passwd *pwd = getpwuid(cmd32->d[0]); + + if (pwd) + printf(" uid %s", pwd->pw_name); + else + printf(" uid %u", cmd32->d[0]); + } + break; + + case O_GID: + { + struct group *grp = getgrgid(cmd32->d[0]); + + if (grp) + printf(" gid %s", grp->gr_name); + else + printf(" gid %u", cmd32->d[0]); + } + break; + + case O_KEEP_STATE: + printf(" keep-state"); + break; + + case O_LIMIT: + { + struct _s_x *p = limit_masks; + ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; + u_int8_t x = c->limit_mask; + char *comma = " "; + + printf(" limit"); + for ( ; p->x != 0 ; p++) + if (x & p->x) { + x &= ~p->x; + printf("%s%s", comma, p->s); + comma = ","; + } + printf(" %d", c->conn_limit); + } + break; + + default: + printf(" [opcode %d len %d]", + cmd->opcode, cmd->len); + } + } + if (cmd->len & F_OR) { + printf(" or"); + or_block = 1; + } else if (or_block) { + printf(" }"); + or_block = 0; + } + } + show_prerequisites(&flags, HAVE_PROTO|HAVE_SRCIP|HAVE_DSTIP); + +#if 0 /* old stuff */ + switch (chain->fw_flg & IP_FW_F_COMMAND) { + case IP_FW_F_REJECT: + if (chain->fw_reject_code == IP_FW_REJECT_RST) + printf("reset"); + else { + printf("unreach "); + print_reject_code(chain->fw_reject_code); + } + break; + } + +do_options: + if (chain->fw_ipflg & IP_FW_IF_IPOPT) + print_flags("ipopt", chain->fw_ipopt, chain->fw_ipnopt, + f_ipopts); + + if (chain->fw_ipflg & IP_FW_IF_IPPRE) + printf(" ipprecedence %u", (chain->fw_iptos & 0xe0) >> 5); + + if (chain->fw_ipflg & IP_FW_IF_IPTOS) + print_flags("iptos", chain->fw_iptos, chain->fw_ipntos, + f_iptos); + + if (chain->fw_flg & IP_FW_F_ICMPBIT) { + int i, first = 1; + unsigned j; + + printf(" icmptype"); + + for (i = 0; i < IP_FW_ICMPTYPES_DIM; ++i) + for (j = 0; j < sizeof(unsigned) * 8; ++j) + if (chain->fw_uar.fw_icmptypes[i] & (1 << j)) { + printf("%c%d", first ? ' ' : ',', + i * sizeof(unsigned) * 8 + j); + first = 0; + } + } +#endif /* XXX old stuff */ + printf("\n"); +} + +static void +show_dyn_ipfw(ipfw_dyn_rule *d) +{ + struct protoent *pe; + struct in_addr a; + + if (!do_expired) { + if (!d->expire && !(d->dyn_type == O_LIMIT_PARENT)) + return; + } + + printf("%05d %10qu %10qu (%ds)", + (int)(d->rule), d->pcnt, d->bcnt, d->expire); + switch (d->dyn_type) { + case O_LIMIT_PARENT: + printf(" PARENT %d", d->count); + break; + case O_LIMIT: + printf(" LIMIT"); + break; + case O_KEEP_STATE: /* bidir, no mask */ + printf(" STATE"); + break; + } + + if ((pe = getprotobynumber(d->id.proto)) != NULL) + printf(" %s", pe->p_name); + else + printf(" proto %u", d->id.proto); + + a.s_addr = htonl(d->id.src_ip); + printf(" %s %d", inet_ntoa(a), d->id.src_port); + + a.s_addr = htonl(d->id.dst_ip); + printf(" <-> %s %d", inet_ntoa(a), d->id.dst_port); + printf("\n"); +} + +int +sort_q(const void *pa, const void *pb) +{ + int rev = (do_sort < 0); + int field = rev ? -do_sort : do_sort; + long long res = 0; + const struct dn_flow_queue *a = pa; + const struct dn_flow_queue *b = pb; + + switch (field) { + case 1: /* pkts */ + res = a->len - b->len; + break; + case 2: /* bytes */ + res = a->len_bytes - b->len_bytes; + break; + + case 3: /* tot pkts */ + res = a->tot_pkts - b->tot_pkts; + break; + + case 4: /* tot bytes */ + res = a->tot_bytes - b->tot_bytes; + break; + } + if (res < 0) + res = -1; + if (res > 0) + res = 1; + return (int)(rev ? res : -res); +} + +static void +list_queues(struct dn_flow_set *fs, struct dn_flow_queue *q) +{ + int l; + + printf(" mask: 0x%02x 0x%08x/0x%04x -> 0x%08x/0x%04x\n", + fs->flow_mask.proto, + fs->flow_mask.src_ip, fs->flow_mask.src_port, + fs->flow_mask.dst_ip, fs->flow_mask.dst_port); + if (fs->rq_elements == 0) + return; + + printf("BKT Prot ___Source IP/port____ " + "____Dest. IP/port____ Tot_pkt/bytes Pkt/Byte Drp\n"); + if (do_sort != 0) + heapsort(q, fs->rq_elements, sizeof *q, sort_q); + for (l = 0; l < fs->rq_elements; l++) { + struct in_addr ina; + struct protoent *pe; + + ina.s_addr = htonl(q[l].id.src_ip); + printf("%3d ", q[l].hash_slot); + pe = getprotobynumber(q[l].id.proto); + if (pe) + printf("%-4s ", pe->p_name); + else + printf("%4u ", q[l].id.proto); + printf("%15s/%-5d ", + inet_ntoa(ina), q[l].id.src_port); + ina.s_addr = htonl(q[l].id.dst_ip); + printf("%15s/%-5d ", + inet_ntoa(ina), q[l].id.dst_port); + printf("%4qu %8qu %2u %4u %3u\n", + q[l].tot_pkts, q[l].tot_bytes, + q[l].len, q[l].len_bytes, q[l].drops); + if (verbose) + printf(" S %20qd F %20qd\n", + q[l].S, q[l].F); + } +} + +static void +print_flowset_parms(struct dn_flow_set *fs, char *prefix) +{ + int l; + char qs[30]; + char plr[30]; + char red[90]; /* Display RED parameters */ + + l = fs->qsize; + if (fs->flags_fs & DN_QSIZE_IS_BYTES) { + if (l >= 8192) + sprintf(qs, "%d KB", l / 1024); + else + sprintf(qs, "%d B", l); + } else + sprintf(qs, "%3d sl.", l); + if (fs->plr) + sprintf(plr, "plr %f", 1.0 * fs->plr / (double)(0x7fffffff)); + else + plr[0] = '\0'; + if (fs->flags_fs & DN_IS_RED) /* RED parameters */ + sprintf(red, + "\n\t %cRED w_q %f min_th %d max_th %d max_p %f", + (fs->flags_fs & DN_IS_GENTLE_RED) ? 'G' : ' ', + 1.0 * fs->w_q / (double)(1 << SCALE_RED), + SCALE_VAL(fs->min_th), + SCALE_VAL(fs->max_th), + 1.0 * fs->max_p / (double)(1 << SCALE_RED)); + else + sprintf(red, "droptail"); + + printf("%s %s%s %d queues (%d buckets) %s\n", + prefix, qs, plr, fs->rq_elements, fs->rq_size, red); +} + +static void +list_pipes(void *data, int nbytes, int ac, char *av[]) +{ + u_long rulenum; + void *next = data; + struct dn_pipe *p = (struct dn_pipe *) data; + struct dn_flow_set *fs; + struct dn_flow_queue *q; + int l; + + if (ac > 0) + rulenum = strtoul(*av++, NULL, 10); + else + rulenum = 0; + for (; nbytes >= sizeof *p; p = (struct dn_pipe *)next) { + double b = p->bandwidth; + char buf[30]; + char prefix[80]; + + if (p->next != (struct dn_pipe *)DN_IS_PIPE) + break; /* done with pipes, now queues */ + + /* + * compute length, as pipe have variable size + */ + l = sizeof(*p) + p->fs.rq_elements * sizeof(*q); + next = (void *)p + l; + nbytes -= l; + + if (rulenum != 0 && rulenum != p->pipe_nr) + continue; + + /* + * Print rate (or clocking interface) + */ + if (p->if_name[0] != '\0') + sprintf(buf, "%s", p->if_name); + else if (b == 0) + sprintf(buf, "unlimited"); + else if (b >= 1000000) + sprintf(buf, "%7.3f Mbit/s", b/1000000); + else if (b >= 1000) + sprintf(buf, "%7.3f Kbit/s", b/1000); + else + sprintf(buf, "%7.3f bit/s ", b); + + sprintf(prefix, "%05d: %s %4d ms ", + p->pipe_nr, buf, p->delay); + print_flowset_parms(&(p->fs), prefix); + if (verbose) + printf(" V %20qd\n", p->V >> MY_M); + + q = (struct dn_flow_queue *)(p+1); + list_queues(&(p->fs), q); + } + for (fs = next; nbytes >= sizeof *fs; fs = next) { + char prefix[80]; + + if (fs->next != (struct dn_flow_set *)DN_IS_QUEUE) + break; + l = sizeof(*fs) + fs->rq_elements * sizeof(*q); + next = (void *)fs + l; + nbytes -= l; + q = (struct dn_flow_queue *)(fs+1); + sprintf(prefix, "q%05d: weight %d pipe %d ", + fs->fs_nr, fs->weight, fs->parent_nr); + print_flowset_parms(fs, prefix); + list_queues(fs, q); + } +} + +static void +list(int ac, char *av[]) +{ + struct ip_fw *r; + ipfw_dyn_rule *dynrules, *d; + + void *lim, *data = NULL; + int n, nbytes, nstat, ndyn; + int exitval = EX_OK; + int lac; + char **lav; + u_long rnum; + char *endptr; + int seen = 0; + + const int ocmd = do_pipe ? IP_DUMMYNET_GET : IP_FW_GET; + int nalloc = 1024; /* start somewhere... */ + + ac--; + av++; + + /* get rules or pipes from kernel, resizing array as necessary */ + nbytes = nalloc; + + while (nbytes >= nalloc) { + nalloc = nalloc * 2 + 200; + nbytes = nalloc; + if ((data = realloc(data, nbytes)) == NULL) + err(EX_OSERR, "realloc"); + if (getsockopt(s, IPPROTO_IP, ocmd, data, &nbytes) < 0) + err(EX_OSERR, "getsockopt(IP_%s_GET)", + do_pipe ? "DUMMYNET" : "FW"); + } + + if (do_pipe) { + list_pipes(data, nbytes, ac, av); + goto done; + } + + /* + * Count static rules. They have variable size so we + * need to scan the list to count them. + */ + for (nstat = 1, r = data, lim = data + nbytes; + r->rulenum < 65535 && (void *)r < lim; + ++nstat, r = (void *)r + RULESIZE(r) ) + ; /* nothing */ + + /* + * Count dynamic rules. This is easier as they have + * fixed size. + */ + r = (void *)r + RULESIZE(r); + dynrules = (ipfw_dyn_rule *)r ; + n = (void *)r - data; + ndyn = (nbytes - n) / sizeof *dynrules; + + /* if no rule numbers were specified, list all rules */ + if (ac == 0) { + for (n = 0, r = data; n < nstat; + n++, r = (void *)r + RULESIZE(r) ) + show_ipfw(r); + + if (do_dynamic && ndyn) { + printf("## Dynamic rules (%d):\n", ndyn); + for (n = 0, d = dynrules; n < ndyn; n++, d++) + show_dyn_ipfw(d); + } + goto done; + } + + /* display specific rules requested on command line */ + + for (lac = ac, lav = av; lac != 0; lac--) { + /* convert command line rule # */ + rnum = strtoul(*lav++, &endptr, 10); + if (*endptr) { + exitval = EX_USAGE; + warnx("invalid rule number: %s", *(lav - 1)); + continue; + } + for (n = seen = 0, r = data; n < nstat; + n++, r = (void *)r + RULESIZE(r) ) { + if (r->rulenum > rnum) + break; + if (r->rulenum == rnum) { + show_ipfw(r); + seen = 1; + } + } + if (!seen) { + /* give precedence to other error(s) */ + if (exitval == EX_OK) + exitval = EX_UNAVAILABLE; + warnx("rule %lu does not exist", rnum); + } + } + + if (do_dynamic && ndyn) { + printf("## Dynamic rules:\n"); + for (lac = ac, lav = av; lac != 0; lac--) { + rnum = strtoul(*lav++, &endptr, 10); + if (*endptr) + /* already warned */ + continue; + for (n = 0, d = dynrules; n < ndyn; n++, d++) { + if ((int)(d->rule) > rnum) + break; + if ((int)(d->rule) == rnum) + show_dyn_ipfw(d); + } + } + } + + ac = 0; + +done: + free(data); + + if (exitval != EX_OK) + exit(exitval); +} + +static void +show_usage(void) +{ + fprintf(stderr, "usage: ipfw [options]\n" +" add [number] rule\n" +" pipe number config [pipeconfig]\n" +" queue number config [queueconfig]\n" +" [pipe] flush\n" +" [pipe] delete number ...\n" +" [pipe] {list|show} [number ...]\n" +" {zero|resetlog} [number ...]\n" +"do \"ipfw -h\" or see ipfw manpage for details\n" +); + + exit(EX_USAGE); +} + +static void +help(void) +{ + + fprintf(stderr, "ipfw syntax summary:\n" +"ipfw add [N] [prob {0..1}] ACTION [log [logamount N]] ADDR OPTIONS\n" +"ipfw {pipe|queue} N config BODY\n" +"ipfw [pipe] {zero|delete|show} [N{,N}]\n" +"\n" +"RULE: [1..] [PROB] BODY\n" +"RULENUM: INTEGER(1..65534)\n" +"PROB: prob REAL(0..1)\n" +"BODY: check-state [LOG] (no body) |\n" +" ACTION [LOG] MATCH_ADDR [OPTION_LIST]\n" +"ACTION: check-state | allow | count | deny | reject | skipto N |\n" +" {divert|tee} PORT | forward ADDR | pipe N | queue N\n" +"ADDR: [ MAC dst src ether_type ] \n" +" [ from IPLIST [ PORT ] to IPLIST [ PORTLIST ] ]\n" +"IPLIST: IPADDR | ( IPADDR or ... or IPADDR )\n" +"IPADDR: [not] { any | me | ip | ip/bits | ip:mask | ip/bits{x,y,z} }\n" +"OPTION_LIST: OPTION [,OPTION_LIST]\n" +); +exit(0); +} + + +static int +lookup_host (char *host, struct in_addr *ipaddr) +{ + struct hostent *he; + + if (!inet_aton(host, ipaddr)) { + if ((he = gethostbyname(host)) == NULL) + return(-1); + *ipaddr = *(struct in_addr *)he->h_addr_list[0]; + } + return(0); +} + +/* + * fills the addr and mask fields in the instruction as appropriate from av. + * Update length as appropriate. + * The following formats are allowed: + * any matches any IP. Actually returns an empty instruction. + * me returns O_IP_*_ME + * 1.2.3.4 single IP address + * 1.2.3.4:5.6.7.8 address:mask + * 1.2.3.4/24 address/mask + * 1.2.3.4/26{1,6,5,4,23} set of addresses in a subnet + */ +static void +fill_ip(ipfw_insn_ip *cmd, char *av) +{ + char *p = 0, md = 0; + u_int32_t i; + + cmd->o.len &= ~F_LEN_MASK; /* zero len */ + + if (!strncmp(av, "any", strlen(av))) + return; + + if (!strncmp(av, "me", strlen(av))) { + cmd->o.len |= F_INSN_SIZE(ipfw_insn); + return; + } + + p = strchr(av, '/'); + if (!p) + p = strchr(av, ':'); + if (p) { + md = *p; + *p++ = '\0'; + } + + if (lookup_host(av, &cmd->addr) != 0) + errx(EX_NOHOST, "hostname ``%s'' unknown", av); + switch (md) { + case ':': + if (!inet_aton(p, &cmd->mask)) + errx(EX_DATAERR, "bad netmask ``%s''", p); + break; + case '/': + i = atoi(p); + if (i == 0) + cmd->mask.s_addr = htonl(0); + else if (i > 32) + errx(EX_DATAERR, "bad width ``%s''", p); + else + cmd->mask.s_addr = htonl(~0 << (32 - i)); + break; + default: + cmd->mask.s_addr = htonl(~0); + break; + } + cmd->addr.s_addr &= cmd->mask.s_addr; + /* + * now look if we have a set of addresses. They are stored as follows: + * arg1 is the set size (powers of 2, 2..256) + * addr is the base address IN HOST FORMAT + * mask.. is an array of u_int32_t with bits set. + */ + if (p) + p = strchr(p, '{'); + if (p) { /* fetch addresses */ + u_int32_t *d; + int low, high; + int i = contigmask((u_char *)&(cmd->mask), 32); + + if (i < 24 || i > 31) { + fprintf(stderr, "invalid set with mask %d\n", + i); + exit(0); + } + cmd->o.arg1 = 1<<(32-i); + cmd->addr.s_addr = ntohl(cmd->addr.s_addr); + d = (u_int32_t *)&cmd->mask; + cmd->o.opcode = O_IP_DST_SET; /* default */ + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32) + (cmd->o.arg1+31)/32; + fprintf(stderr,"-- set size %d cmdlen %d\n", + cmd->o.arg1, cmd->o.len ); + for (i = 0; i < cmd->o.arg1/32 ; i++) + d[i] = 0; /* clear masks */ + + av = p+1; + low = cmd->addr.s_addr & 0xff; + high = low + cmd->o.arg1 - 1; + while (isdigit(*av)) { + char *s; + u_int16_t a = strtol(av, &s, 0); + + if (s == av) /* no parameter */ + break; + if (a < low || a > high) { + fprintf(stderr, "addr %d out of range [%d-%d]\n", + a, low, high); + exit(0); + } + a -= low; + d[ a/32] |= 1<<(a & 31); + if (*s != ',') + break; + av = s+1; + } + return; + } + + if (cmd->mask.s_addr == 0) { /* any */ + if (cmd->o.len & F_NOT) + errx(EX_DATAERR, "not any never matches"); + else /* useless, nuke it */ + return; + } else if (cmd->mask.s_addr == IP_MASK_ALL) /* one IP */ + cmd->o.len |= F_INSN_SIZE(ipfw_insn_u32); + else /* addr/mask */ + cmd->o.len |= F_INSN_SIZE(ipfw_insn_ip); +} + + +/* + * helper function to process a set of flags and set bits in the + * appropriate masks. + */ +static void +fill_flags(ipfw_insn *cmd, enum ipfw_opcodes opcode, + struct _s_x *flags, char *p) +{ + u_int8_t set=0, clear=0; + + while (p && *p) { + char *q; /* points to the separator */ + int val; + u_int8_t *which; /* mask we are working on */ + + if (*p == '!') { + p++; + which = &clear; + } else + which = &set; + q = strchr(p, ','); + if (q) + *q++ = '\0'; + val = match_token(flags, p); + if (val <= 0) + errx(EX_DATAERR, "invalid flag %s", p); + *which |= (u_int8_t)val; + p = q; + } + cmd->opcode = opcode; + cmd->len = (cmd->len & (F_NOT | F_OR)) | 1; + cmd->arg1 = (set & 0xff) | ( (clear & 0xff) << 8); +} + +#if 0 /* XXX todo */ +static void +fill_icmptypes(unsigned *types, char **vp, u_int *fw_flg) +{ + unsigned long icmptype; + char *c = *vp; + + while (*c) { + if (*c == ',') + ++c; + + icmptype = strtoul(c, &c, 0); + + if (*c != ',' && *c != '\0') + errx(EX_DATAERR, "invalid ICMP type"); + + if (icmptype >= IP_FW_ICMPTYPES_DIM * sizeof(unsigned) * 8) + errx(EX_DATAERR, "ICMP type out of range"); + + types[icmptype / (sizeof(unsigned) * 8)] |= + 1 << (icmptype % (sizeof(unsigned) * 8)); + *fw_flg |= IP_FW_F_ICMPBIT; + } +} +#endif /* XXX todo */ + +static void +delete(int ac, char *av[]) +{ + int rulenum; + struct dn_pipe pipe; + int i; + int exitval = EX_OK; + + memset(&pipe, 0, sizeof pipe); + + av++; ac--; + + /* Rule number */ + while (ac && isdigit(**av)) { + i = atoi(*av); av++; ac--; + if (do_pipe) { + if (do_pipe == 1) + pipe.pipe_nr = i; + else + pipe.fs.fs_nr = i; + i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_DEL, + &pipe, sizeof pipe); + if (i) { + exitval = 1; + warn("rule %u: setsockopt(IP_DUMMYNET_DEL)", + do_pipe == 1 ? pipe.pipe_nr : + pipe.fs.fs_nr); + } + } else { + rulenum = i; + i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rulenum, + sizeof rulenum); + if (i) { + exitval = EX_UNAVAILABLE; + warn("rule %u: setsockopt(IP_FW_DEL)", + rulenum); + } + } + } + if (exitval != EX_OK) + exit(exitval); +} + + +/* + * fill the interface structure. We do not check the name as we can + * create interfaces dynamically, so checking them at insert time + * makes relatively little sense. + * A '*' following the name means any unit. + */ +static void +fill_iface(ipfw_insn_if *cmd, char *arg) +{ + cmd->name[0] = '\0'; + cmd->o.len |= F_INSN_SIZE(ipfw_insn_if); + + /* Parse the interface or address */ + if (!strcmp(arg, "any")) + cmd->o.len = 0; /* effectively ignore this command */ + else if (!isdigit(*arg)) { + char *q; + + strncpy(cmd->name, arg, sizeof(cmd->name)); + cmd->name[sizeof(cmd->name) - 1] = '\0'; + /* find first digit or wildcard */ + for (q = cmd->name; *q && !isdigit(*q) && *q != '*'; q++) + continue; + cmd->p.unit = (*q == '*') ? -1 : atoi(q); + *q = '\0'; + } else if (!inet_aton(arg, &cmd->p.ip)) + errx(EX_DATAERR, "bad ip address ``%s''", arg); +} + +/* + * the following macro returns an error message if we run out of + * arguments. + */ +#define NEED1(msg) {if (!ac) errx(EX_USAGE, msg);} + +static void +config_pipe(int ac, char **av) +{ + struct dn_pipe pipe; + int i; + char *end; + u_int32_t a; + void *par = NULL; + + memset(&pipe, 0, sizeof pipe); + + av++; ac--; + /* Pipe number */ + if (ac && isdigit(**av)) { + i = atoi(*av); av++; ac--; + if (do_pipe == 1) + pipe.pipe_nr = i; + else + pipe.fs.fs_nr = i; + } + while (ac > 1) { + double d; + int tok = match_token(dummynet_params, *av); + ac--; av++; + + switch(tok) { + case TOK_PLR: + NEED1("plr needs argument 0..1\n"); + d = strtod(av[0], NULL); + if (d > 1) + d = 1; + else if (d < 0) + d = 0; + pipe.fs.plr = (int)(d*0x7fffffff); + ac--; av++; + break; + + case TOK_QUEUE: + NEED1("queue needs queue size\n"); + end = NULL; + pipe.fs.qsize = strtoul(av[0], &end, 0); + if (*end == 'K' || *end == 'k') { + pipe.fs.flags_fs |= DN_QSIZE_IS_BYTES; + pipe.fs.qsize *= 1024; + } else if (*end == 'B' || !strncmp(end, "by", 2)) { + pipe.fs.flags_fs |= DN_QSIZE_IS_BYTES; + } + ac--; av++; + break; + + case TOK_BUCKETS: + NEED1("buckets needs argument\n"); + pipe.fs.rq_size = strtoul(av[0], NULL, 0); + ac--; av++; + break; + + case TOK_MASK: + NEED1("mask needs mask specifier\n"); + /* + * per-flow queue, mask is dst_ip, dst_port, + * src_ip, src_port, proto measured in bits + */ + par = NULL; + + pipe.fs.flow_mask.dst_ip = 0; + pipe.fs.flow_mask.src_ip = 0; + pipe.fs.flow_mask.dst_port = 0; + pipe.fs.flow_mask.src_port = 0; + pipe.fs.flow_mask.proto = 0; + end = NULL; + + while (ac >= 1) { + u_int32_t *p32 = NULL; + u_int16_t *p16 = NULL; + + tok = match_token(dummynet_params, *av); + ac--; av++; + switch(tok) { + case TOK_ALL: + /* + * special case, all bits significant + */ + pipe.fs.flow_mask.dst_ip = ~0; + pipe.fs.flow_mask.src_ip = ~0; + pipe.fs.flow_mask.dst_port = ~0; + pipe.fs.flow_mask.src_port = ~0; + pipe.fs.flow_mask.proto = ~0; + pipe.fs.flags_fs |= DN_HAVE_FLOW_MASK; + goto end_mask; + + case TOK_DSTIP: + p32 = &pipe.fs.flow_mask.dst_ip; + break; + + case TOK_SRCIP: + p32 = &pipe.fs.flow_mask.src_ip; + break; + + case TOK_DSTPORT: + p16 = &pipe.fs.flow_mask.dst_port; + break; + + case TOK_SRCPORT: + p16 = &pipe.fs.flow_mask.src_port; + break; + + case TOK_PROTO: + break; + + default: + ac++; av--; /* backtrack */ + goto end_mask; + } + if (ac < 1) + errx(EX_USAGE, "mask: value missing"); + if (*av[0] == '/') { + a = strtoul(av[0]+1, &end, 0); + a = (a == 32) ? ~0 : (1 << a) - 1; + } else + a = strtoul(av[1], &end, 0); + if (p32 != NULL) + *p32 = a; + else if (p16 != NULL) { + if (a > 65535) + errx(EX_DATAERR, + "mask: must be 16 bit"); + *p16 = (u_int16_t)a; + } else { + if (a > 255) + errx(EX_DATAERR, + "mask: must be 8 bit"); + pipe.fs.flow_mask.proto = (u_int8_t)a; + } + if (a != 0) + pipe.fs.flags_fs |= DN_HAVE_FLOW_MASK; + ac--; av++; + } /* end while, config masks */ +end_mask: + break; + + case TOK_RED: + case TOK_GRED: + NEED1("red/gred needs w_q/min_th/max_th/max_p\n"); + pipe.fs.flags_fs |= DN_IS_RED; + if (tok == TOK_GRED) + pipe.fs.flags_fs |= DN_IS_GENTLE_RED; + /* + * the format for parameters is w_q/min_th/max_th/max_p + */ + if ((end = strsep(&av[0], "/"))) { + double w_q = strtod(end, NULL); + if (w_q > 1 || w_q <= 0) + errx(EX_DATAERR, "0 < w_q <= 1"); + pipe.fs.w_q = (int) (w_q * (1 << SCALE_RED)); + } + if ((end = strsep(&av[0], "/"))) { + pipe.fs.min_th = strtoul(end, &end, 0); + if (*end == 'K' || *end == 'k') + pipe.fs.min_th *= 1024; + } + if ((end = strsep(&av[0], "/"))) { + pipe.fs.max_th = strtoul(end, &end, 0); + if (*end == 'K' || *end == 'k') + pipe.fs.max_th *= 1024; + } + if ((end = strsep(&av[0], "/"))) { + double max_p = strtod(end, NULL); + if (max_p > 1 || max_p <= 0) + errx(EX_DATAERR, "0 < max_p <= 1"); + pipe.fs.max_p = (int)(max_p * (1 << SCALE_RED)); + } + ac--; av++; + break; + + case TOK_DROPTAIL: + pipe.fs.flags_fs &= ~(DN_IS_RED|DN_IS_GENTLE_RED); + break; + + case TOK_BW: + NEED1("bw needs bandwidth or interface\n"); + if (do_pipe != 1) + errx(EX_DATAERR, "bandwidth only valid for pipes"); + /* + * set clocking interface or bandwidth value + */ + if (av[0][0] >= 'a' && av[0][0] <= 'z') { + int l = sizeof(pipe.if_name)-1; + /* interface name */ + strncpy(pipe.if_name, av[0], l); + pipe.if_name[l] = '\0'; + pipe.bandwidth = 0; + } else { + pipe.if_name[0] = '\0'; + pipe.bandwidth = strtoul(av[0], &end, 0); + if (*end == 'K' || *end == 'k') { + end++; + pipe.bandwidth *= 1000; + } else if (*end == 'M') { + end++; + pipe.bandwidth *= 1000000; + } + if (*end == 'B' || !strncmp(end, "by", 2)) + pipe.bandwidth *= 8; + if (pipe.bandwidth < 0) + errx(EX_DATAERR, "bandwidth too large"); + } + ac--; av++; + break; + + case TOK_DELAY: + if (do_pipe != 1) + errx(EX_DATAERR, "delay only valid for pipes"); + NEED1("delay needs argument 0..10000ms\n"); + pipe.delay = strtoul(av[0], NULL, 0); + ac--; av++; + break; + + case TOK_WEIGHT: + if (do_pipe == 1) + errx(EX_DATAERR,"weight only valid for queues"); + NEED1("weight needs argument 0..100\n"); + pipe.fs.weight = strtoul(av[0], &end, 0); + ac--; av++; + break; + + case TOK_PIPE: + if (do_pipe == 1) + errx(EX_DATAERR,"pipe only valid for queues"); + NEED1("pipe needs pipe_number\n"); + pipe.fs.parent_nr = strtoul(av[0], &end, 0); + ac--; av++; + break; + + default: + errx(EX_DATAERR, "unrecognised option ``%s''", *av); + } + } + if (do_pipe == 1) { + if (pipe.pipe_nr == 0) + errx(EX_DATAERR, "pipe_nr must be > 0"); + if (pipe.delay > 10000) + errx(EX_DATAERR, "delay must be < 10000"); + } else { /* do_pipe == 2, queue */ + if (pipe.fs.parent_nr == 0) + errx(EX_DATAERR, "pipe must be > 0"); + if (pipe.fs.weight >100) + errx(EX_DATAERR, "weight must be <= 100"); + } + if (pipe.fs.flags_fs & DN_QSIZE_IS_BYTES) { + if (pipe.fs.qsize > 1024*1024) + errx(EX_DATAERR, "queue size must be < 1MB"); + } else { + if (pipe.fs.qsize > 100) + errx(EX_DATAERR, "2 <= queue size <= 100"); + } + if (pipe.fs.flags_fs & DN_IS_RED) { + size_t len; + int lookup_depth, avg_pkt_size; + double s, idle, weight, w_q; + struct clockinfo clock; + int t; + + if (pipe.fs.min_th >= pipe.fs.max_th) + errx(EX_DATAERR, "min_th %d must be < than max_th %d", + pipe.fs.min_th, pipe.fs.max_th); + if (pipe.fs.max_th == 0) + errx(EX_DATAERR, "max_th must be > 0"); + + len = sizeof(int); + if (sysctlbyname("net.inet.ip.dummynet.red_lookup_depth", + &lookup_depth, &len, NULL, 0) == -1) + + errx(1, "sysctlbyname(\"%s\")", + "net.inet.ip.dummynet.red_lookup_depth"); + if (lookup_depth == 0) + errx(EX_DATAERR, "net.inet.ip.dummynet.red_lookup_depth" + " must be greater than zero"); + + len = sizeof(int); + if (sysctlbyname("net.inet.ip.dummynet.red_avg_pkt_size", + &avg_pkt_size, &len, NULL, 0) == -1) + + errx(1, "sysctlbyname(\"%s\")", + "net.inet.ip.dummynet.red_avg_pkt_size"); + if (avg_pkt_size == 0) + errx(EX_DATAERR, + "net.inet.ip.dummynet.red_avg_pkt_size must" + " be greater than zero"); + + len = sizeof(struct clockinfo); + if (sysctlbyname("kern.clockrate", &clock, &len, NULL, 0) == -1) + errx(1, "sysctlbyname(\"%s\")", "kern.clockrate"); + + /* + * Ticks needed for sending a medium-sized packet. + * Unfortunately, when we are configuring a WF2Q+ queue, we + * do not have bandwidth information, because that is stored + * in the parent pipe, and also we have multiple queues + * competing for it. So we set s=0, which is not very + * correct. But on the other hand, why do we want RED with + * WF2Q+ ? + */ + if (pipe.bandwidth==0) /* this is a WF2Q+ queue */ + s = 0; + else + s = clock.hz * avg_pkt_size * 8 / pipe.bandwidth; + + /* + * max idle time (in ticks) before avg queue size becomes 0. + * NOTA: (3/w_q) is approx the value x so that + * (1-w_q)^x < 10^-3. + */ + w_q = ((double)pipe.fs.w_q) / (1 << SCALE_RED); + idle = s * 3. / w_q; + pipe.fs.lookup_step = (int)idle / lookup_depth; + if (!pipe.fs.lookup_step) + pipe.fs.lookup_step = 1; + weight = 1 - w_q; + for (t = pipe.fs.lookup_step; t > 0; --t) + weight *= weight; + pipe.fs.lookup_weight = (int)(weight * (1 << SCALE_RED)); + } + i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_CONFIGURE, &pipe, + sizeof pipe); + if (i) + err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE"); +} + +static void +get_mac_addr_mask(char *p, u_char *addr, u_char *mask) +{ + int i, l; + + for (i=0; i<6; i++) + addr[i] = mask[i] = 0; + if (!strcmp(p, "any")) + return; + + for (i=0; *p && i<6;i++, p++) { + addr[i] = strtol(p, &p, 16); + if (*p != ':') /* we start with the mask */ + break; + } + if (*p == '/') { /* mask len */ + l = strtol(p+1, &p, 0); + for (i=0; l>0; l -=8, i++) + mask[i] = (l >=8) ? 0xff : (~0) << (8-l); + } else if (*p == '&') { /* mask */ + for (i=0, p++; *p && i<6;i++, p++) { + mask[i] = strtol(p, &p, 16); + if (*p != ':') + break; + } + } else if (*p == '\0') { + for (i=0; i<6; i++) + mask[i] = 0xff; + } + for (i=0; i<6; i++) + addr[i] &= mask[i]; +} + +/* + * helper function, updates the pointer to cmd with the length + * of the current command, and also cleans up the first word of + * the new command in case it has been clobbered before. + */ +static ipfw_insn * +next_cmd(ipfw_insn *cmd) +{ + cmd += F_LEN(cmd); + bzero(cmd, sizeof(*cmd)); + return cmd; +} + +/* + * A function to fill simple commands of size 1. + * Existing flags are preserved. + */ +static void +fill_cmd(ipfw_insn *cmd, enum ipfw_opcodes opcode, int flags, u_int16_t arg) +{ + cmd->opcode = opcode; + cmd->len = ((cmd->len | flags) & (F_NOT | F_OR)) | 1; + cmd->arg1 = arg; +} + +/* + * Fetch and add the MAC address and type, with masks. This generates one or + * two microinstructions, and returns the pointer to the last one. + */ +static ipfw_insn * +add_mac(ipfw_insn *cmd, int ac, char *av[]) +{ + ipfw_insn_mac *mac; /* also *src */ + + if (ac <3) + errx(EX_DATAERR, "MAC dst src type"); + + cmd->opcode = O_MACADDR2; + cmd->len = (cmd->len & (F_NOT | F_OR)) | F_INSN_SIZE(ipfw_insn_mac); + + mac = (ipfw_insn_mac *)cmd; + get_mac_addr_mask(av[0], mac->addr, mac->mask); /* dst */ + get_mac_addr_mask(av[1], &(mac->addr[6]), &(mac->mask[6])); /* src */ + av += 2; + + if (strcmp(av[0], "any") != 0) { /* we have a non-null port */ + cmd += F_LEN(cmd); + + fill_newports((ipfw_insn_u16 *)cmd, av[0], IPPROTO_ETHERTYPE); + cmd->opcode = O_MAC_TYPE; + } + + return cmd; +} + +/* + * Parse arguments and assemble the microinstructions which make up a rule. + * Rules are added into the 'rulebuf' and then copied in the correct order + * into the actual rule. + * + * The syntax for a rule starts with the action, followed by an + * optional log action, and the various match patterns. + * In the assembled microcode, the first opcode must be a O_PROBE_STATE + * (generated if the rule includes a keep-state option), then the + * various match patterns, the "log" action, and the actual action. + * + */ +static void +add(int ac, char *av[]) +{ + /* + * rules are added into the 'rulebuf' and then copied in + * the correct order into the actual rule. + * Some things that need to go out of order (prob, action etc.) + * go into actbuf[]. + */ + static u_int32_t rulebuf[255], actbuf[255], cmdbuf[255]; + + ipfw_insn *src, *dst, *cmd, *action, *prev; + + struct ip_fw *rule; + + /* + * various flags used to record that we entered some fields. + */ + int have_mac = 0; /* set if we have a MAC address */ + int have_state = 0; /* check-state or keep-state */ + + int i; + + int open_par = 0; /* open parenthesis ( */ + + /* proto is here because it is used to fetch ports */ + u_char proto = IPPROTO_IP; /* default protocol */ + + bzero(actbuf, sizeof(actbuf)); /* actions go here */ + bzero(cmdbuf, sizeof(cmdbuf)); + bzero(rulebuf, sizeof(rulebuf)); + + rule = (struct ip_fw *)rulebuf; + cmd = (ipfw_insn *)cmdbuf; + action = (ipfw_insn *)actbuf; + + av++; ac--; + + /* [rule N] -- Rule number optional */ + if (ac && isdigit(**av)) { + rule->rulenum = atoi(*av); + av++; + ac--; + } + + /* [prob D] -- match probability, optional */ + if (ac > 1 && !strncmp(*av, "prob", strlen(*av))) { + double d = strtod(av[1], NULL); + + if (d <= 0 || d > 1) + errx(EX_DATAERR, "illegal match prob. %s", av[1]); + if (d != 1) { /* 1 means always match */ + action->opcode = O_PROB; + action->len = 2; + *((int32_t *)(action+1)) = + (int32_t)((1 - d) * 0x7fffffff); + action += action->len; + } + av += 2; ac -= 2; + } + + /* action -- mandatory */ + NEED1("missing action"); + i = match_token(rule_actions, *av); + ac--; av++; + action->len = 1; /* default */ + switch(i) { + case TOK_CHECKSTATE: + have_state = 1; + action->opcode = O_CHECK_STATE; + break; + + case TOK_ACCEPT: + action->opcode = O_ACCEPT; + break; + + case TOK_DENY: + action->opcode = O_DENY; + break; + + case TOK_COUNT: + action->opcode = O_COUNT; + break; + + case TOK_QUEUE: + case TOK_PIPE: + action->len = F_INSN_SIZE(ipfw_insn_pipe); + case TOK_SKIPTO: + if (i == TOK_QUEUE) + action->opcode = O_QUEUE; + else if (i == TOK_PIPE) + action->opcode = O_PIPE; + else if (i == TOK_SKIPTO) + action->opcode = O_SKIPTO; + NEED1("missing skipto/pipe/queue number"); + action->arg1 = strtoul(*av, NULL, 10); + av++; ac--; + break; + + case TOK_DIVERT: + case TOK_TEE: + action->opcode = (i == TOK_DIVERT) ? O_DIVERT : O_TEE; + NEED1("missing divert/tee port"); + action->arg1 = strtoul(*av, NULL, 0); + if (action->arg1 == 0) { + struct servent *s; + setservent(1); + s = getservbyname(av[0], "divert"); + if (s != NULL) + action->arg1 = ntohs(s->s_port); + else + errx(EX_DATAERR, "illegal divert/tee port"); + } + ac--; av++; + break; + + case TOK_FORWARD: { + ipfw_insn_sa *p = (ipfw_insn_sa *)action; + char *s, *end; + + NEED1("missing forward address[:port]"); + + action->opcode = O_FORWARD_IP; + action->len = F_INSN_SIZE(ipfw_insn_sa); + + p->sa.sin_len = sizeof(struct sockaddr_in); + p->sa.sin_family = AF_INET; + p->sa.sin_port = 0; + /* + * locate the address-port separator (':' or ',') + */ + s = strchr(*av, ':'); + if (s == NULL) + s = strchr(*av, ','); + if (s != NULL) { + *(s++) = '\0'; + i = strtoport(s, &end, 0 /* base */, 0 /* proto */); + if (s == end) + errx(EX_DATAERR, + "illegal forwarding port ``%s''", s); + p->sa.sin_port = htons( (u_short)i ); + } + lookup_host(*av, &(p->sa.sin_addr)); + } + ac--; av++; + break; + + default: + errx(EX_DATAERR, "invalid action %s\n", *av); + } + action = next_cmd(action); + +#if 0 + } else if (!strncmp(*av, "reject", strlen(*av))) { + rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; + rule.fw_reject_code = ICMP_UNREACH_HOST; + } else if (!strncmp(*av, "reset", strlen(*av))) { + rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; + rule.fw_reject_code = ICMP_REJECT_RST; /* check TCP later */ + } else if (!strncmp(*av, "unreach", strlen(*av))) { + rule.fw_flg |= IP_FW_F_REJECT; av++; ac--; + fill_reject_code(&rule.fw_reject_code, *av); av++; ac--; + } +#endif /* XXX other actions */ + + /* + * [log [logamount N]] -- log, optional + * + * If exists, it goes first in the cmdbuf, but then it is + * skipped in the copy section to the end of the buffer. + */ + if (ac && !strncmp(*av, "log", strlen(*av))) { + ipfw_insn_log *c = (ipfw_insn_log *)cmd; + + cmd->len = F_INSN_SIZE(ipfw_insn_log); + cmd->opcode = O_LOG; + av++; ac--; + if (ac && !strncmp(*av, "logamount", strlen(*av))) { + ac--; av++; + NEED1("logamount requires argument"); + c->max_log = atoi(*av); + if (c->max_log < 0) + errx(EX_DATAERR, "logamount must be positive"); + ac--; av++; + } + cmd = next_cmd(cmd); + } + + if (have_state) { + have_state = 0; + goto done; + } + +#define OR_START(target) \ + if (ac && (*av[0] == '(' || *av[0] == '{')) { \ + if (open_par) \ + errx(EX_USAGE, "nested \"(\" not allowed\n"); \ + open_par = 1; \ + if ( (av[0])[1] == '\0') { \ + ac--; av++; \ + } else \ + (*av)++; \ + } \ + target: \ + + +#define CLOSE_PAR \ + if (open_par) { \ + if (ac && ( \ + !strncmp(*av, ")", strlen(*av)) || \ + !strncmp(*av, "}", strlen(*av)) )) { \ + open_par = 0; \ + ac--; av++; \ + } else \ + errx(EX_USAGE, "missing \")\"\n"); \ + } + +#define NOT_BLOCK \ + if (ac && !strncmp(*av, "not", strlen(*av))) { \ + if (cmd->len & F_NOT) \ + errx(EX_USAGE, "double \"not\" not allowed\n"); \ + cmd->len |= F_NOT; \ + ac--; av++; \ + } + +#define OR_BLOCK(target) \ + if (ac && !strncmp(*av, "or", strlen(*av))) { \ + if (prev == NULL || open_par == 0) \ + errx(EX_DATAERR, "invalid OR block"); \ + prev->len |= F_OR; \ + ac--; av++; \ + goto target; \ + } \ + CLOSE_PAR; + + /* + * protocol, mandatory + */ + OR_START(get_proto); + NOT_BLOCK; + NEED1("missing protocol"); + { + struct protoent *pe; + + if (!strncmp(*av, "all", strlen(*av))) + ; /* same as "ip" */ + else if (!strncmp(*av, "MAC", strlen(*av))) { + /* need exactly 3 fields */ + cmd = add_mac(cmd, ac-1, av+1); /* exits in case of errors */ + ac -= 3; + av += 3; + have_mac = 1; + } else if ((proto = atoi(*av)) > 0) + ; /* all done! */ + else if ((pe = getprotobyname(*av)) != NULL) + proto = pe->p_proto; + else + errx(EX_DATAERR, "invalid protocol ``%s''", *av); + av++; ac--; + if (proto != IPPROTO_IP) + fill_cmd(cmd, O_PROTO, 0, proto); + } + cmd = next_cmd(cmd); + OR_BLOCK(get_proto); + + /* + * "from", mandatory (unless we have a MAC address) + */ + if (!ac || strncmp(*av, "from", strlen(*av))) { + if (have_mac) /* we do not need a "to" address */ + goto read_to; + errx(EX_USAGE, "missing ``from''"); + } + ac--; av++; + + /* + * source IP, mandatory + */ + OR_START(source_ip); + NOT_BLOCK; /* optional "not" */ + NEED1("missing source address"); + + /* source -- mandatory */ + fill_ip((ipfw_insn_ip *)cmd, *av); + if (cmd->opcode == O_IP_DST_SET) /* set */ + cmd->opcode = O_IP_SRC_SET; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ + cmd->opcode = O_IP_SRC_ME; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ + cmd->opcode = O_IP_SRC; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_ip)) /* addr/mask */ + cmd->opcode = O_IP_SRC_MASK; + /* otherwise len will be zero and the command skipped */ + ac--; av++; + prev = cmd; /* in case we need to backtrack */ + cmd = next_cmd(cmd); + OR_BLOCK(source_ip); + + /* + * source ports, optional + */ + NOT_BLOCK; /* optional "not" */ + if (ac && fill_newports((ipfw_insn_u16 *)cmd, *av, proto)) { + /* XXX todo: check that we have a protocol with ports */ + cmd->opcode = O_IP_SRCPORT; + ac--; + av++; + cmd = next_cmd(cmd); + } + +read_to: + /* + * "to", mandatory (unless we have a MAC address + */ + if (!ac || strncmp(*av, "to", strlen(*av))) { + if (have_mac) + goto read_options; + errx(EX_USAGE, "missing ``to''"); + } + av++; ac--; + + /* + * destination, mandatory + */ + OR_START(dest_ip); + NOT_BLOCK; /* optional "not" */ + NEED1("missing dst address"); + fill_ip((ipfw_insn_ip *)cmd, *av); + if (cmd->opcode == O_IP_DST_SET) /* set */ + ; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn)) /* me */ + cmd->opcode = O_IP_DST_ME; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_u32)) /* one IP */ + cmd->opcode = O_IP_DST; + else if (F_LEN(cmd) == F_INSN_SIZE(ipfw_insn_ip)) /* addr/mask */ + cmd->opcode = O_IP_DST_MASK; + ac--; + av++; + prev = cmd; + cmd = next_cmd(cmd); + OR_BLOCK(dest_ip); + + /* + * dest. ports, optional + */ + NOT_BLOCK; /* optional "not" */ + if (ac && fill_newports((ipfw_insn_u16 *)cmd, *av, proto)) { + /* XXX todo: check that we have a protocol with ports */ + cmd->opcode = O_IP_DSTPORT; + ac--; + av++; + cmd += F_LEN(cmd); + } + +read_options: + prev = NULL; + while (ac) { + char *s = *av; + ipfw_insn_u32 *cmd32 = (ipfw_insn_u32 *)cmd; /* alias */ + + if (*s == '!') { /* alternate syntax for NOT */ + if (cmd->len & F_NOT) + errx(EX_USAGE, "double \"not\" not allowed\n"); + cmd->len = F_NOT; + s++; + } + i = match_token(rule_options, s); + ac--; av++; + switch(i) { + case TOK_NOT: + if (cmd->len & F_NOT) + errx(EX_USAGE, "double \"not\" not allowed\n"); + cmd->len = F_NOT; + break; + + case TOK_OR: + if (prev == NULL) + errx(EX_USAGE, "invalid \"or\" block\n"); + prev->len |= F_OR; + break; + + case TOK_IN: + fill_cmd(cmd, O_IN, 0, 0); + break; + + case TOK_OUT: + cmd->len ^= F_NOT; /* toggle F_NOT */ + fill_cmd(cmd, O_IN, 0, 0); + break; + + case TOK_FRAG: + fill_cmd(cmd, O_FRAG, 0, 0); + break; + + case TOK_LAYER2: + fill_cmd(cmd, O_LAYER2, 0, 0); + break; + + case TOK_XMIT: + case TOK_RECV: + case TOK_VIA: + NEED1("recv, xmit, via require interface name" + " or address"); + fill_iface((ipfw_insn_if *)cmd, av[0]); + ac--; av++; + if (F_LEN(cmd) == 0) /* not a valid address */ + break; + if (i == TOK_XMIT) + cmd->opcode = O_XMIT; + else if (i == TOK_RECV) + cmd->opcode = O_RECV; + else if (i == TOK_VIA) + cmd->opcode = O_VIA; + break; + + case TOK_IPTTL: + NEED1("ipttl requires TTL"); + fill_cmd(cmd, O_IPTTL, 0, strtoul(*av, NULL, 0)); + ac--; av++; + break; + + case TOK_IPID: + NEED1("ipid requires length"); + fill_cmd(cmd, O_IPID, 0, strtoul(*av, NULL, 0)); + ac--; av++; + break; + + case TOK_IPLEN: + NEED1("iplen requires length"); + fill_cmd(cmd, O_IPLEN, 0, strtoul(*av, NULL, 0)); + ac--; av++; + break; + + case TOK_IPVER: + NEED1("ipver requires version"); + fill_cmd(cmd, O_IPVER, 0, strtoul(*av, NULL, 0)); + ac--; av++; + break; + + case TOK_IPOPTS: + NEED1("missing argument for ipoptions"); + fill_flags(cmd, O_IPOPTS, f_ipopts, *av); + ac--; av++; + break; + + case TOK_UID: + NEED1("uid requires argument"); + { + char *end; + uid_t uid; + struct passwd *pwd; + + cmd->opcode = O_UID; + uid = strtoul(*av, &end, 0); + pwd = (*end == '\0') ? getpwuid(uid) : getpwnam(*av); + if (pwd == NULL) + errx(EX_DATAERR, "uid \"%s\" nonexistent", *av); + cmd32->d[0] = uid; + cmd->len = F_INSN_SIZE(ipfw_insn_u32); + ac--; av++; + } + break; + + case TOK_GID: + NEED1("gid requires argument"); + { + char *end; + gid_t gid; + struct group *grp; + + cmd->opcode = O_GID; + gid = strtoul(*av, &end, 0); + grp = (*end == '\0') ? getgrgid(gid) : getgrnam(*av); + if (grp == NULL) + errx(EX_DATAERR, "gid \"%s\" nonexistent", *av); + + cmd32->d[0] = gid; + cmd->len = F_INSN_SIZE(ipfw_insn_u32); + ac--; av++; + } + break; + + case TOK_ESTAB: + fill_cmd(cmd, O_ESTAB, 0, 0); + break; + + case TOK_SETUP: + fill_cmd(cmd, O_TCPFLAGS, 0, + (TH_SYN) | ( (TH_ACK) & 0xff) <<8 ); + break; + + case TOK_TCPOPTS: + NEED1("missing argument for tcpoptions"); + fill_flags(cmd, O_TCPOPTS, f_tcpopts, *av); + ac--; av++; + break; + + case TOK_TCPSEQ: + case TOK_TCPACK: + NEED1("tcpseq/tcpack requires argument"); + cmd->len = F_INSN_SIZE(ipfw_insn_u32); + cmd->opcode = (i == TOK_TCPSEQ) ? O_TCPSEQ : O_TCPACK; + cmd32->d[0] = htonl(strtoul(*av, NULL, 0)); + ac--; av++; + break; + + case TOK_TCPWIN: + NEED1("tcpwin requires length"); + fill_cmd(cmd, O_TCPWIN, 0, + htons(strtoul(*av, NULL, 0))); + ac--; av++; + break; + + case TOK_TCPFLAGS: + NEED1("missing argument for tcpflags"); + cmd->opcode = O_TCPFLAGS; + fill_flags(cmd, O_TCPFLAGS, f_tcpflags, *av); + ac--; av++; + break; + + case TOK_KEEPSTATE: + have_state = 1; + fill_cmd(cmd, O_KEEP_STATE, 0, 0); + break; + + case TOK_LIMIT: + NEED1("limit needs mask and # of connections"); + { + ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; + + cmd->len = F_INSN_SIZE(ipfw_insn_limit); + cmd->opcode = O_LIMIT; + c->limit_mask = 0; + c->conn_limit = 0; + for (; ac >1 ;) { + int val; + + val = match_token(limit_masks, *av); + if (val <= 0) + break; + c->limit_mask |= val; + ac--; av++; + } + c->conn_limit = atoi(*av); + if (c->conn_limit == 0) + errx(EX_USAGE, "limit: limit must be >0"); + if (c->limit_mask == 0) + errx(EX_USAGE, "missing limit mask"); + ac--; av++; + have_state = 1; + } + break; + + default: + errx(EX_USAGE, "unrecognised option [%d] %s\n", i, s); + } + if (F_LEN(cmd) > 0) { /* prepare to advance */ + prev = cmd; + cmd = next_cmd(cmd); + } + } +#if 0 /* XXX todo */ +do_options: + while (ac) { + } else if (!strncmp(*av, "ipprecedence", strlen(*av))) { + u_long ippre; + char *c; + + av++; ac--; + NEED1("missing argument for ``ipprecedence''"); + ippre = strtoul(*av, &c, 0); + if (*c != '\0') + errx(EX_DATAERR, "argument to ipprecedence" + " must be numeric"); + if (ippre > 7) + errx(EX_DATAERR, "argument to ipprecedence" + " out of range"); + rule.fw_ipflg |= IP_FW_IF_IPPRE; + rule.fw_iptos |= (u_short)(ippre << 5); + av++; ac--; + } else if (!strncmp(*av, "iptos", strlen(*av))) { + av++; ac--; + NEED1("missing argument for ``iptos''"); + rule.fw_ipflg |= IP_FW_IF_IPTOS; + fill_flags(&rule.fw_iptos, &rule.fw_ipntos, + f_iptos, av); + av++; ac--; + } else if (rule.fw_prot == IPPROTO_ICMP) { + if (!strncmp(*av, "icmptypes", strlen(*av))) { + av++; ac--; + NEED1("missing argument for ``icmptypes''"); + fill_icmptypes(rule.fw_uar.fw_icmptypes, + av, &rule.fw_flg); + av++; ac--; + } + } + } +#endif /* XXX todo */ +done: + /* + * Now copy stuff into the rule. + * If we have a keep-state option, the first instruction + * must be a PROBE_STATE (which is generated here). + * If we have a LOG option, it was stored as the first command, + * and now must be moved to the top of the action part. + */ + dst = (ipfw_insn *)rule->cmd; + + /* + * generate O_PROBE_STATE if necessary + */ + if (have_state) { + fill_cmd(dst, O_PROBE_STATE, 0, 0); + dst = next_cmd(dst); + } + /* + * copy all commands but O_LOG + */ + for (src = (ipfw_insn *)cmdbuf; src != cmd; src += i) { + i = F_LEN(src); + + if (src->opcode != O_LOG) { + bcopy(src, dst, i * sizeof(u_int32_t)); + dst += i; + } + } + + /* + * start action section + */ + rule->act_ofs = dst - rule->cmd; + + /* + * put back O_LOG if necessary + */ + src = (ipfw_insn *)cmdbuf; + if ( src->opcode == O_LOG ) { + i = F_LEN(src); + bcopy(src, dst, i * sizeof(u_int32_t)); + dst += i; + } + /* + * copy all other actions + */ + for (src = (ipfw_insn *)actbuf; src != action; src += i) { + i = F_LEN(src); + bcopy(src, dst, i * sizeof(u_int32_t)); + dst += i; + } + + rule->cmd_len = (u_int32_t *)dst - (u_int32_t *)(rule->cmd); + i = (void *)dst - (void *)rule; + if (!do_quiet) + show_ipfw(rule); + if (getsockopt(s, IPPROTO_IP, IP_FW_ADD, rule, &i) == -1) + err(EX_UNAVAILABLE, "getsockopt(%s)", "IP_FW_ADD"); + if (!do_quiet) + show_ipfw(rule); +} + +static void +zero (int ac, char *av[]) +{ + int rulenum; + int failed = EX_OK; + + av++; ac--; + + if (!ac) { + /* clear all entries */ + if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO, NULL, 0) < 0) + err(EX_UNAVAILABLE, "setsockopt(%s)", "IP_FW_ZERO"); + if (!do_quiet) + printf("Accounting cleared.\n"); + + return; + } + + while (ac) { + /* Rule number */ + if (isdigit(**av)) { + rulenum = atoi(*av); + av++; + ac--; + if (setsockopt(s, IPPROTO_IP, + IP_FW_ZERO, &rulenum, sizeof rulenum)) { + warn("rule %u: setsockopt(IP_FW_ZERO)", + rulenum); + failed = EX_UNAVAILABLE; + } else if (!do_quiet) + printf("Entry %d cleared\n", rulenum); + } else { + errx(EX_USAGE, "invalid rule number ``%s''", *av); + } + } + if (failed != EX_OK) + exit(failed); +} + +static void +resetlog (int ac, char *av[]) +{ + int rulenum; + int failed = EX_OK; + + av++; ac--; + + if (!ac) { + /* clear all entries */ + if (setsockopt(s, IPPROTO_IP, IP_FW_RESETLOG, NULL, 0) < 0) + err(EX_UNAVAILABLE, "setsockopt(IP_FW_RESETLOG)"); + if (!do_quiet) + printf("Logging counts reset.\n"); + + return; + } + + while (ac) { + /* Rule number */ + if (isdigit(**av)) { + rulenum = atoi(*av); + av++; + ac--; + if (setsockopt(s, IPPROTO_IP, + IP_FW_RESETLOG, &rulenum, sizeof rulenum)) { + warn("rule %u: setsockopt(IP_FW_RESETLOG)", + rulenum); + failed = EX_UNAVAILABLE; + } else if (!do_quiet) + printf("Entry %d logging count reset\n", + rulenum); + } else { + errx(EX_DATAERR, "invalid rule number ``%s''", *av); + } + } + if (failed != EX_OK) + exit(failed); +} + +static void +flush() +{ + int cmd = do_pipe ? IP_DUMMYNET_FLUSH : IP_FW_FLUSH; + + if (!do_force && !do_quiet) { /* need to ask user */ + int c; + + printf("Are you sure? [yn] "); + fflush(stdout); + do { + c = toupper(getc(stdin)); + while (c != '\n' && getc(stdin) != '\n') + if (feof(stdin)) + return; /* and do not flush */ + } while (c != 'Y' && c != 'N'); + printf("\n"); + if (c == 'N') /* user said no */ + return; + } + if (setsockopt(s, IPPROTO_IP, cmd, NULL, 0) < 0) + err(EX_UNAVAILABLE, "setsockopt(IP_%s_FLUSH)", + do_pipe ? "DUMMYNET" : "FW"); + if (!do_quiet) + printf("Flushed all %s.\n", do_pipe ? "pipes" : "rules"); +} + +static int +ipfw_main(int ac, char **av) +{ + int ch; + + if (ac == 1) + show_usage(); + + /* Set the force flag for non-interactive processes */ + do_force = !isatty(STDIN_FILENO); + + optind = optreset = 1; + while ((ch = getopt(ac, av, "hs:adefNqtv")) != -1) + switch (ch) { + case 'h': /* help */ + help(); + break; /* NOTREACHED */ + + case 's': /* sort */ + do_sort = atoi(optarg); + break; + case 'a': + do_acct = 1; + break; + case 'd': + do_dynamic = 1; + break; + case 'e': + do_expired = 1; + break; + case 'f': + do_force = 1; + break; + case 'N': + do_resolv = 1; + break; + case 'q': + do_quiet = 1; + break; + case 't': + do_time = 1; + break; + case 'v': /* verbose */ + verbose++; + break; + default: + show_usage(); + } + + ac -= optind; + av += optind; + NEED1("bad arguments, for usage summary ``ipfw''"); + + /* + * optional: pipe or queue + */ + if (!strncmp(*av, "pipe", strlen(*av))) { + do_pipe = 1; + ac--; + av++; + } else if (!strncmp(*av, "queue", strlen(*av))) { + do_pipe = 2; + ac--; + av++; + } + NEED1("missing command"); + + /* + * for pipes and queues we normally say 'pipe NN config' + * but the code is easier to parse as 'pipe config NN' + * so we swap the two arguments. + */ + if (do_pipe > 0 && ac > 1 && *av[0] >= '0' && *av[0] <= '9') { + char *p = av[0]; + av[0] = av[1]; + av[1] = p; + } + if (!strncmp(*av, "add", strlen(*av))) + add(ac, av); + else if (do_pipe && !strncmp(*av, "config", strlen(*av))) + config_pipe(ac, av); + else if (!strncmp(*av, "delete", strlen(*av))) + delete(ac, av); + else if (!strncmp(*av, "flush", strlen(*av))) + flush(); + else if (!strncmp(*av, "zero", strlen(*av))) + zero(ac, av); + else if (!strncmp(*av, "resetlog", strlen(*av))) + resetlog(ac, av); + else if (!strncmp(*av, "print", strlen(*av)) || + !strncmp(*av, "list", strlen(*av))) + list(ac, av); + else if (!strncmp(*av, "show", strlen(*av))) { + do_acct++; + list(ac, av); + } else + errx(EX_USAGE, "bad command `%s'", *av); + return 0; +} + + +static void +ipfw_readfile(int ac, char *av[]) +{ +#define MAX_ARGS 32 +#define WHITESP " \t\f\v\n\r" + char buf[BUFSIZ]; + char *a, *p, *args[MAX_ARGS], *cmd = NULL; + char linename[10]; + int i=0, lineno=0, qflag=0, pflag=0, status; + FILE *f = NULL; + pid_t preproc = 0; + int c; + + while ((c = getopt(ac, av, "D:U:p:q")) != -1) + switch(c) { + case 'D': + if (!pflag) + errx(EX_USAGE, "-D requires -p"); + if (i > MAX_ARGS - 2) + errx(EX_USAGE, + "too many -D or -U options"); + args[i++] = "-D"; + args[i++] = optarg; + break; + + case 'U': + if (!pflag) + errx(EX_USAGE, "-U requires -p"); + if (i > MAX_ARGS - 2) + errx(EX_USAGE, + "too many -D or -U options"); + args[i++] = "-U"; + args[i++] = optarg; + break; + + case 'p': + pflag = 1; + cmd = optarg; + args[0] = cmd; + i = 1; + break; + + case 'q': + qflag = 1; + break; + + default: + errx(EX_USAGE, "bad arguments, for usage" + " summary ``ipfw''"); + } + + av += optind; + ac -= optind; + if (ac != 1) + errx(EX_USAGE, "extraneous filename arguments"); + + if ((f = fopen(av[0], "r")) == NULL) + err(EX_UNAVAILABLE, "fopen: %s", av[0]); + + if (pflag) { + /* pipe through preprocessor (cpp or m4) */ + int pipedes[2]; + + args[i] = 0; + + if (pipe(pipedes) == -1) + err(EX_OSERR, "cannot create pipe"); + + switch((preproc = fork())) { + case -1: + err(EX_OSERR, "cannot fork"); + + case 0: + /* child */ + if (dup2(fileno(f), 0) == -1 + || dup2(pipedes[1], 1) == -1) + err(EX_OSERR, "dup2()"); + fclose(f); + close(pipedes[1]); + close(pipedes[0]); + execvp(cmd, args); + err(EX_OSERR, "execvp(%s) failed", cmd); + + default: + /* parent */ + fclose(f); + close(pipedes[1]); + if ((f = fdopen(pipedes[0], "r")) == NULL) { + int savederrno = errno; + + (void)kill(preproc, SIGTERM); + errno = savederrno; + err(EX_OSERR, "fdopen()"); + } + } + } + + while (fgets(buf, BUFSIZ, f)) { + lineno++; + sprintf(linename, "Line %d", lineno); + args[0] = linename; + + if (*buf == '#') + continue; + if ((p = strchr(buf, '#')) != NULL) + *p = '\0'; + i = 1; + if (qflag) + args[i++] = "-q"; + for (a = strtok(buf, WHITESP); + a && i < MAX_ARGS; a = strtok(NULL, WHITESP), i++) + args[i] = a; + if (i == (qflag? 2: 1)) + continue; + if (i == MAX_ARGS) + errx(EX_USAGE, "%s: too many arguments", + linename); + args[i] = NULL; + + ipfw_main(i, args); + } + fclose(f); + if (pflag) { + if (waitpid(preproc, &status, 0) == -1) + errx(EX_OSERR, "waitpid()"); + if (WIFEXITED(status) && WEXITSTATUS(status) != EX_OK) + errx(EX_UNAVAILABLE, + "preprocessor exited with status %d", + WEXITSTATUS(status)); + else if (WIFSIGNALED(status)) + errx(EX_UNAVAILABLE, + "preprocessor exited with signal %d", + WTERMSIG(status)); + } +} + +int +main(int ac, char *av[]) +{ + s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); + if (s < 0) + err(EX_UNAVAILABLE, "socket"); + + setbuf(stdout, 0); + + /* + * If the last argument is an absolute pathname, interpret it + * as a file to be preprocessed. + */ + + if (ac > 1 && av[ac - 1][0] == '/' && access(av[ac - 1], R_OK) == 0) + ipfw_readfile(ac, av); + else + ipfw_main(ac, av); + return EX_OK; +} |