Index: head/sbin/ipfw/ipfw.c =================================================================== --- head/sbin/ipfw/ipfw.c (revision 83724) +++ head/sbin/ipfw/ipfw.c (revision 83725) @@ -1,2564 +1,2575 @@ /* * 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 * */ #ifndef lint static const char rcsid[] = "$FreeBSD$"; #endif /* not lint */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* def. of struct route */ #include #include #include 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; struct icmpcode { int code; char *str; }; static struct icmpcode icmpcodes[] = { { ICMP_UNREACH_NET, "net" }, { ICMP_UNREACH_HOST, "host" }, { ICMP_UNREACH_PROTOCOL, "protocol" }, { ICMP_UNREACH_PORT, "port" }, { ICMP_UNREACH_NEEDFRAG, "needfrag" }, { ICMP_UNREACH_SRCFAIL, "srcfail" }, { ICMP_UNREACH_NET_UNKNOWN, "net-unknown" }, { ICMP_UNREACH_HOST_UNKNOWN, "host-unknown" }, { ICMP_UNREACH_ISOLATED, "isolated" }, { ICMP_UNREACH_NET_PROHIB, "net-prohib" }, { ICMP_UNREACH_HOST_PROHIB, "host-prohib" }, { ICMP_UNREACH_TOSNET, "tosnet" }, { ICMP_UNREACH_TOSHOST, "toshost" }, { ICMP_UNREACH_FILTER_PROHIB, "filter-prohib" }, { ICMP_UNREACH_HOST_PRECEDENCE, "host-precedence" }, { ICMP_UNREACH_PRECEDENCE_CUTOFF, "precedence-cutoff" }, { 0, NULL } }; static void show_usage(void); static int mask_bits(struct in_addr m_ad) { int h_fnd = 0, h_num = 0, i; u_long mask; mask = ntohl(m_ad.s_addr); for (i = 0; i < sizeof(u_long)*CHAR_BIT; i++) { if (mask & 1L) { h_fnd = 1; h_num++; } else { if (h_fnd) return -1; } mask = mask >> 1; } return h_num; } static void print_port(u_char prot, u_short port, const char comma) { struct servent *se = NULL; struct protoent *pe; if (comma == ':') { printf("%c0x%04x", comma, port); return; } if (do_resolv) { pe = getprotobynumber(prot); se = getservbyport(htons(port), pe ? pe->p_name : NULL); } if (se) printf("%c%s", comma, se->s_name); else printf("%c%d", comma, port); } static void print_iface(char *key, union ip_fw_if *un, int byname) { char ifnb[FW_IFNLEN+1]; if (byname) { strncpy(ifnb, un->fu_via_if.name, FW_IFNLEN); ifnb[FW_IFNLEN] = '\0'; if (un->fu_via_if.unit == -1) printf(" %s %s*", key, ifnb); else printf(" %s %s%d", key, ifnb, un->fu_via_if.unit); } else if (un->fu_via_ip.s_addr != 0) { printf(" %s %s", key, inet_ntoa(un->fu_via_ip)); } else printf(" %s any", key); } static void print_reject_code(int code) { struct icmpcode *ic; for (ic = icmpcodes; ic->str; ic++) if (ic->code == code) { printf("%s", ic->str); return; } printf("%u", code); } static void show_ipfw(struct ip_fw *chain) { char comma; u_long adrt; struct hostent *he; struct protoent *pe; int i, mb; int nsp = IP_FW_GETNSRCP(chain); int ndp = IP_FW_GETNDSTP(chain); if (do_resolv) setservent(1/*stay open*/); printf("%05u ", chain->fw_number); if (do_acct) printf("%10qu %10qu ", chain->fw_pcnt, chain->fw_bcnt); if (do_time) { if (chain->timestamp) { char timestr[30]; strcpy(timestr, ctime((time_t *)&chain->timestamp)); *strchr(timestr, '\n') = '\0'; printf("%s ", timestr); } else { printf(" "); } } if (chain->fw_flg == IP_FW_F_CHECK_S) { printf("check-state\n"); goto done; } if (chain->fw_flg & IP_FW_F_RND_MATCH) { double d = 1.0 * (int)(chain->pipe_ptr); d = 1 - (d / 0x7fffffff); printf("prob %f ", d); } switch (chain->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_ACCEPT: printf("allow"); break; case IP_FW_F_DENY: printf("deny"); break; case IP_FW_F_COUNT: printf("count"); break; case IP_FW_F_DIVERT: printf("divert %u", chain->fw_divert_port); break; case IP_FW_F_TEE: printf("tee %u", chain->fw_divert_port); break; case IP_FW_F_SKIPTO: printf("skipto %u", chain->fw_skipto_rule); break; case IP_FW_F_PIPE: printf("pipe %u", chain->fw_skipto_rule); break; case IP_FW_F_QUEUE: printf("queue %u", chain->fw_skipto_rule); break; 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; case IP_FW_F_FWD: printf("fwd %s", inet_ntoa(chain->fw_fwd_ip.sin_addr)); if(chain->fw_fwd_ip.sin_port) printf(",%d", chain->fw_fwd_ip.sin_port); break; default: errx(EX_OSERR, "impossible"); } if (chain->fw_flg & IP_FW_F_PRN) { printf(" log"); if (chain->fw_logamount) printf(" logamount %d", chain->fw_logamount); } pe = getprotobynumber(chain->fw_prot); if (pe) printf(" %s", pe->p_name); else printf(" %u", chain->fw_prot); if (chain->fw_flg & IP_FW_F_SME) { printf(" from me"); } else { printf(" from %s", chain->fw_flg & IP_FW_F_INVSRC ? "not " : ""); adrt = ntohl(chain->fw_smsk.s_addr); if (adrt == ULONG_MAX && do_resolv) { adrt = (chain->fw_src.s_addr); he = gethostbyaddr((char *)&adrt, sizeof(u_long), AF_INET); if (he == NULL) printf("%s", inet_ntoa(chain->fw_src)); else printf("%s", he->h_name); } else if (adrt != ULONG_MAX) { mb = mask_bits(chain->fw_smsk); if (mb == 0) { printf("any"); } else if (mb > 0) { printf("%s", inet_ntoa(chain->fw_src)); printf("/%d", mb); } else { printf("%s", inet_ntoa(chain->fw_src)); printf(":"); printf("%s", inet_ntoa(chain->fw_smsk)); } } else { printf("%s", inet_ntoa(chain->fw_src)); } } if (chain->fw_prot == IPPROTO_TCP || chain->fw_prot == IPPROTO_UDP) { comma = ' '; for (i = 0; i < nsp; i++) { print_port(chain->fw_prot, chain->fw_uar.fw_pts[i], comma); if (i == 0 && (chain->fw_flg & IP_FW_F_SRNG)) comma = '-'; else if (i == 0 && (chain->fw_flg & IP_FW_F_SMSK)) comma = ':'; else comma = ','; } } if (chain->fw_flg & IP_FW_F_DME) { printf(" to me"); } else { printf(" to %s", chain->fw_flg & IP_FW_F_INVDST ? "not " : ""); adrt = ntohl(chain->fw_dmsk.s_addr); if (adrt == ULONG_MAX && do_resolv) { adrt = (chain->fw_dst.s_addr); he = gethostbyaddr((char *)&adrt, sizeof(u_long), AF_INET); if (he == NULL) printf("%s", inet_ntoa(chain->fw_dst)); else printf("%s", he->h_name); } else if (adrt != ULONG_MAX) { mb = mask_bits(chain->fw_dmsk); if (mb == 0) { printf("any"); } else if (mb > 0) { printf("%s", inet_ntoa(chain->fw_dst)); printf("/%d", mb); } else { printf("%s", inet_ntoa(chain->fw_dst)); printf(":"); printf("%s", inet_ntoa(chain->fw_dmsk)); } } else { printf("%s", inet_ntoa(chain->fw_dst)); } } if (chain->fw_prot == IPPROTO_TCP || chain->fw_prot == IPPROTO_UDP) { comma = ' '; for (i = 0; i < ndp; i++) { print_port(chain->fw_prot, chain->fw_uar.fw_pts[nsp+i], comma); if (i == 0 && (chain->fw_flg & IP_FW_F_DRNG)) comma = '-'; else if (i == 0 && (chain->fw_flg & IP_FW_F_DMSK)) comma = ':'; else comma = ','; } } if (chain->fw_flg & IP_FW_F_UID) { struct passwd *pwd = getpwuid(chain->fw_uid); if (pwd) printf(" uid %s", pwd->pw_name); else printf(" uid %u", chain->fw_uid); } if (chain->fw_flg & IP_FW_F_GID) { struct group *grp = getgrgid(chain->fw_gid); if (grp) printf(" gid %s", grp->gr_name); else printf(" gid %u", chain->fw_gid); } if (chain->fw_flg & IP_FW_F_KEEP_S) { - if (chain->next_rule_ptr) - printf(" keep-state %d", (int)chain->next_rule_ptr); - else - printf(" keep-state"); + u_long x = (u_long)chain->next_rule_ptr; + u_char type = (x) & 0xff ; + + switch(type) { + default: + printf(" *** unknown type ***"); + break ; + case DYN_KEEP_STATE: + printf(" keep-state"); + break; + } } /* Direction */ if (chain->fw_flg & IP_FW_BRIDGED) printf(" bridged"); if ((chain->fw_flg & IP_FW_F_IN) && !(chain->fw_flg & IP_FW_F_OUT)) printf(" in"); if (!(chain->fw_flg & IP_FW_F_IN) && (chain->fw_flg & IP_FW_F_OUT)) printf(" out"); /* Handle hack for "via" backwards compatibility */ if ((chain->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { print_iface("via", &chain->fw_in_if, chain->fw_flg & IP_FW_F_IIFNAME); } else { /* Receive interface specified */ if (chain->fw_flg & IP_FW_F_IIFACE) print_iface("recv", &chain->fw_in_if, chain->fw_flg & IP_FW_F_IIFNAME); /* Transmit interface specified */ if (chain->fw_flg & IP_FW_F_OIFACE) print_iface("xmit", &chain->fw_out_if, chain->fw_flg & IP_FW_F_OIFNAME); } if (chain->fw_flg & IP_FW_F_FRAG) printf(" frag"); if (chain->fw_ipflg & IP_FW_IF_IPOPT) { int _opt_printed = 0; #define PRINTOPT(x) {if (_opt_printed) printf(",");\ printf(x); _opt_printed = 1;} printf(" ipopt "); if (chain->fw_ipopt & IP_FW_IPOPT_SSRR) PRINTOPT("ssrr"); if (chain->fw_ipnopt & IP_FW_IPOPT_SSRR) PRINTOPT("!ssrr"); if (chain->fw_ipopt & IP_FW_IPOPT_LSRR) PRINTOPT("lsrr"); if (chain->fw_ipnopt & IP_FW_IPOPT_LSRR) PRINTOPT("!lsrr"); if (chain->fw_ipopt & IP_FW_IPOPT_RR) PRINTOPT("rr"); if (chain->fw_ipnopt & IP_FW_IPOPT_RR) PRINTOPT("!rr"); if (chain->fw_ipopt & IP_FW_IPOPT_TS) PRINTOPT("ts"); if (chain->fw_ipnopt & IP_FW_IPOPT_TS) PRINTOPT("!ts"); } if (chain->fw_ipflg & IP_FW_IF_IPLEN) printf(" iplen %u", chain->fw_iplen); if (chain->fw_ipflg & IP_FW_IF_IPID) printf(" ipid %#x", chain->fw_ipid); if (chain->fw_ipflg & IP_FW_IF_IPTOS) { int _opt_printed = 0; printf(" iptos "); if (chain->fw_iptos & IPTOS_LOWDELAY) PRINTOPT("lowdelay"); if (chain->fw_ipntos & IPTOS_LOWDELAY) PRINTOPT("!lowdelay"); if (chain->fw_iptos & IPTOS_THROUGHPUT) PRINTOPT("throughput"); if (chain->fw_ipntos & IPTOS_THROUGHPUT) PRINTOPT("!throughput"); if (chain->fw_iptos & IPTOS_RELIABILITY) PRINTOPT("reliability"); if (chain->fw_ipntos & IPTOS_RELIABILITY) PRINTOPT("!reliability"); if (chain->fw_iptos & IPTOS_MINCOST) PRINTOPT("mincost"); if (chain->fw_ipntos & IPTOS_MINCOST) PRINTOPT("!mincost"); if (chain->fw_iptos & IPTOS_CE) PRINTOPT("congestion"); if (chain->fw_ipntos & IPTOS_CE) PRINTOPT("!congestion"); } if (chain->fw_ipflg & IP_FW_IF_IPTTL) printf(" ipttl %u", chain->fw_ipttl); if (chain->fw_ipflg & IP_FW_IF_IPVER) printf(" ipversion %u", chain->fw_ipver); if (chain->fw_ipflg & IP_FW_IF_TCPEST) printf(" established"); else if (chain->fw_tcpf == IP_FW_TCPF_SYN && chain->fw_tcpnf == IP_FW_TCPF_ACK) printf(" setup"); else if (chain->fw_ipflg & IP_FW_IF_TCPFLG) { int _flg_printed = 0; #define PRINTFLG(x) {if (_flg_printed) printf(",");\ printf(x); _flg_printed = 1;} printf(" tcpflags "); if (chain->fw_tcpf & IP_FW_TCPF_FIN) PRINTFLG("fin"); if (chain->fw_tcpnf & IP_FW_TCPF_FIN) PRINTFLG("!fin"); if (chain->fw_tcpf & IP_FW_TCPF_SYN) PRINTFLG("syn"); if (chain->fw_tcpnf & IP_FW_TCPF_SYN) PRINTFLG("!syn"); if (chain->fw_tcpf & IP_FW_TCPF_RST) PRINTFLG("rst"); if (chain->fw_tcpnf & IP_FW_TCPF_RST) PRINTFLG("!rst"); if (chain->fw_tcpf & IP_FW_TCPF_PSH) PRINTFLG("psh"); if (chain->fw_tcpnf & IP_FW_TCPF_PSH) PRINTFLG("!psh"); if (chain->fw_tcpf & IP_FW_TCPF_ACK) PRINTFLG("ack"); if (chain->fw_tcpnf & IP_FW_TCPF_ACK) PRINTFLG("!ack"); if (chain->fw_tcpf & IP_FW_TCPF_URG) PRINTFLG("urg"); if (chain->fw_tcpnf & IP_FW_TCPF_URG) PRINTFLG("!urg"); } if (chain->fw_ipflg & IP_FW_IF_TCPOPT) { int _opt_printed = 0; #define PRINTTOPT(x) {if (_opt_printed) printf(",");\ printf(x); _opt_printed = 1;} printf(" tcpoptions "); if (chain->fw_tcpopt & IP_FW_TCPOPT_MSS) PRINTTOPT("mss"); if (chain->fw_tcpnopt & IP_FW_TCPOPT_MSS) PRINTTOPT("!mss"); if (chain->fw_tcpopt & IP_FW_TCPOPT_WINDOW) PRINTTOPT("window"); if (chain->fw_tcpnopt & IP_FW_TCPOPT_WINDOW) PRINTTOPT("!window"); if (chain->fw_tcpopt & IP_FW_TCPOPT_SACK) PRINTTOPT("sack"); if (chain->fw_tcpnopt & IP_FW_TCPOPT_SACK) PRINTTOPT("!sack"); if (chain->fw_tcpopt & IP_FW_TCPOPT_TS) PRINTTOPT("ts"); if (chain->fw_tcpnopt & IP_FW_TCPOPT_TS) PRINTTOPT("!ts"); if (chain->fw_tcpopt & IP_FW_TCPOPT_CC) PRINTTOPT("cc"); if (chain->fw_tcpnopt & IP_FW_TCPOPT_CC) PRINTTOPT("!cc"); } if (chain->fw_ipflg & IP_FW_IF_TCPSEQ) printf(" tcpseq %lu", (u_long)ntohl(chain->fw_tcpseq)); if (chain->fw_ipflg & IP_FW_IF_TCPACK) printf(" tcpack %lu", (u_long)ntohl(chain->fw_tcpack)); if (chain->fw_ipflg & IP_FW_IF_TCPWIN) printf(" tcpwin %hu", ntohs(chain->fw_tcpwin)); 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; } } printf("\n"); done: if (do_resolv) endservent(); } static void show_dyn_ipfw(struct ipfw_dyn_rule *d) { struct protoent *pe; struct in_addr a; if (!d->expire && !do_expired) return; - printf("%05d %qu %qu (T %d, # %d) ty %d", + printf("%05d %qu %qu (T %ds, slot %d)", (int)(d->chain), d->pcnt, d->bcnt, d->expire, - d->bucket, - d->type); + d->bucket); + switch (d->dyn_type) { + case DYN_KEEP_STATE: /* bidir, no mask */ + printf(" <->"); + break; + } if (do_resolv && (pe = getprotobynumber(d->id.proto)) != NULL) printf(" %s,", pe->p_name); else printf(" %u,", d->id.proto); a.s_addr = htonl(d->id.src_ip); printf(" %s", inet_ntoa(a)); printf(" %d", d->id.src_port); - switch (d->type) { + switch (d->dyn_type) { default: /* bidir, no mask */ printf(" <->"); break; } a.s_addr = htonl(d->id.dst_ip); printf(" %s", inet_ntoa(a)); printf(" %d", 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 %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(int ac, char *av[]) { struct ip_fw *rules, *r; struct ipfw_dyn_rule *dynrules, *d; struct dn_pipe *pipes; void *data = NULL; int n, nbytes, nstat, ndyn; int exitval = EX_OK; int lac; char **lav; u_long rnum; char *endptr; int seen = 0; /* get rules or pipes from kernel, resizing array as necessary */ { const int unit = do_pipe ? sizeof(*pipes) : sizeof(*rules); const int ocmd = do_pipe ? IP_DUMMYNET_GET : IP_FW_GET; int nalloc = unit; 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"); } } /* display requested pipes */ if (do_pipe) { 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; l = sizeof(*p) + p->fs.rq_elements * sizeof(*q); next = (void *)p + l; nbytes -= l; q = (struct dn_flow_queue *)(p+1); if (rulenum != 0 && rulenum != p->pipe_nr) continue; 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); list_queues(&(p->fs), q); } fs = (struct dn_flow_set *) next; for (; nbytes >= sizeof *fs; fs = (struct dn_flow_set *)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); } free(data); return; } rules = (struct ip_fw *)data; for (nstat = 0; rules[nstat].fw_number < 65535; ++nstat) /* nothing */ ; nstat++; /* counting starts from 0 ... */ dynrules = (struct ipfw_dyn_rule *)&rules[nstat]; ndyn = (nbytes - (nstat * sizeof *rules)) / sizeof *dynrules; /* if no rule numbers were specified, list all rules */ if (ac == 0) { for (n = 0; n < nstat; n++) show_ipfw(&rules[n]); if (do_dynamic && ndyn) { printf("## Dynamic rules:\n"); for (n = 0, d = dynrules; n < ndyn; n++, d++) show_dyn_ipfw(d); } free(data); return; } /* 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 = rules; n < nstat; n++, r++) { if (r->fw_number > rnum) break; if (r->fw_number == 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); } } printf("## Dynamic rules:\n"); if (do_dynamic && ndyn) { 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->chain) > rnum) break; if ((int)(d->chain) == rnum) show_dyn_ipfw(d); } } } ac = 0; free(data); if (exitval != EX_OK) exit(exitval); } static void show_usage(void) { fprintf(stderr, "usage: ipfw [options]\n" " [pipe] flush\n" " add [number] rule\n" " [pipe] delete number ...\n" " [pipe] list [number ...]\n" " [pipe] show [number ...]\n" " zero [number ...]\n" " resetlog [number ...]\n" " pipe number config [pipeconfig]\n" " rule: [prob ] action proto src dst extras...\n" " action:\n" " {allow|permit|accept|pass|deny|drop|reject|unreach code|\n" " reset|count|skipto num|divert port|tee port|fwd ip|\n" " pipe num} [log [logamount count]]\n" " proto: {ip|tcp|udp|icmp| < number>}\n" " src: from [not] {me|any|ip[{/bits|:mask}]} [{port[-port]}, [port], ...]\n" " dst: to [not] {me|any|ip[{/bits|:mask}]} [{port[-port]}, [port], ...]\n" " extras:\n" " uid {user id}\n" " gid {group id}\n" " fragment (may not be used with ports or tcpflags)\n" " in\n" " out\n" " {xmit|recv|via} {iface|ip|any}\n" " {established|setup}\n" " tcpflags [!]{syn|fin|rst|ack|psh|urg}, ...\n" " ipoptions [!]{ssrr|lsrr|rr|ts}, ...\n" " iplen {length}\n" " ipid {identification number}\n" " iptos [!]{lowdelay|throughput|reliability|mincost|congestion}, ...\n" " ipttl {time to live}\n" " ipversion {version number}\n" " tcpoptions [!]{mss|window|sack|ts|cc}, ...\n" " tcpseq {sequence number}\n" " tcpack {acknowledgement number}\n" " tcpwin {window size}\n" " icmptypes {type[, type]}...\n" " keep-state [method]\n" " pipeconfig:\n" " {bw|bandwidth} {bit/s|Kbit/s|Mbit/s|Bytes/s|KBytes/s|MBytes/s}\n" " {bw|bandwidth} interface_name\n" " delay \n" " queue {packets|Bytes|KBytes}\n" " plr \n" " mask {all| [dst-ip|src-ip|dst-port|src-port|proto] }\n" " buckets }\n" " {red|gred} ///\n" " droptail\n" ); exit(EX_USAGE); } 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); } static void fill_ip(struct in_addr *ipno, struct in_addr *mask, int *acp, char ***avp) { int ac = *acp; char **av = *avp; char *p = 0, md = 0; if (ac && !strncmp(*av, "any", strlen(*av))) { ipno->s_addr = mask->s_addr = 0; av++; ac--; } else { p = strchr(*av, '/'); if (!p) p = strchr(*av, ':'); if (p) { md = *p; *p++ = '\0'; } if (lookup_host(*av, ipno) != 0) errx(EX_NOHOST, "hostname ``%s'' unknown", *av); switch (md) { case ':': if (!inet_aton(p, mask)) errx(EX_DATAERR, "bad netmask ``%s''", p); break; case '/': if (atoi(p) == 0) { mask->s_addr = 0; } else if (atoi(p) > 32) { errx(EX_DATAERR, "bad width ``%s''", p); } else { mask->s_addr = htonl(~0 << (32 - atoi(p))); } break; default: mask->s_addr = htonl(~0); break; } ipno->s_addr &= mask->s_addr; av++; ac--; } *acp = ac; *avp = av; } static void fill_reject_code(u_short *codep, char *str) { struct icmpcode *ic; u_long val; char *s; val = strtoul(str, &s, 0); if (s != str && *s == '\0' && val < 0x100) { *codep = val; return; } for (ic = icmpcodes; ic->str; ic++) if (!strcasecmp(str, ic->str)) { *codep = ic->code; return; } errx(EX_DATAERR, "unknown ICMP unreachable code ``%s''", str); } static void add_port(u_short *cnt, u_short *ptr, u_short off, u_short port) { if (off + *cnt >= IP_FW_MAX_PORTS) errx(EX_USAGE, "too many ports (max is %d)", IP_FW_MAX_PORTS); ptr[off+*cnt] = port; (*cnt)++; } static int lookup_port(const char *arg, int proto, int test, int nodash) { int val; char *earg, buf[32]; struct servent *s; char *p, *q; snprintf(buf, sizeof(buf), "%s", arg); for (p = q = buf; *p; *q++ = *p++) { if (*p == '\\') { if (*(p+1)) p++; } else { if (*p == ',' || (nodash && *p == '-')) break; } } *q = '\0'; val = (int) strtoul(buf, &earg, 0); if (!*buf || *earg) { char *protocol = NULL; if (proto != 0) { struct protoent *pe = getprotobynumber(proto); if (pe) protocol = pe->p_name; } setservent(1); if ((s = getservbyname(buf, protocol))) { val = htons(s->s_port); } else { if (!test) errx(EX_DATAERR, "unknown port ``%s''", buf); val = -1; } } else { if (val < 0 || val > 0xffff) { if (!test) errx(EX_DATAERR, "port ``%s'' out of range", buf); val = -1; } } return(val); } /* * return: 0 normally, 1 if first pair is a range, * 2 if first pair is a port+mask */ static int fill_port(u_short *cnt, u_short *ptr, u_short off, char *arg, int proto) { char *s; int initial_range = 0; for (s = arg; *s && *s != ',' && *s != '-' && *s != ':'; s++) { if (*s == '\\' && *(s+1)) s++; } if (*s == ':') { *s++ = '\0'; if (strchr(arg, ',')) errx(EX_USAGE, "port/mask must be first in list"); add_port(cnt, ptr, off, *arg ? lookup_port(arg, proto, 0, 0) : 0x0000); arg = s; s = strchr(arg, ','); if (s) *s++ = '\0'; add_port(cnt, ptr, off, *arg ? lookup_port(arg, proto, 0, 0) : 0xffff); arg = s; initial_range = 2; } else if (*s == '-') { *s++ = '\0'; if (strchr(arg, ',')) errx(EX_USAGE, "port range must be first in list"); add_port(cnt, ptr, off, *arg ? lookup_port(arg, proto, 0, 0) : 0x0000); arg = s; s = strchr(arg, ','); if (s) *s++ = '\0'; add_port(cnt, ptr, off, *arg ? lookup_port(arg, proto, 0, 0) : 0xffff); arg = s; initial_range = 1; } while (arg != NULL) { s = strchr(arg, ','); if (s) *s++ = '\0'; add_port(cnt, ptr, off, lookup_port(arg, proto, 0, 0)); arg = s; } return initial_range; } static void fill_tcpflag(u_char *set, u_char *reset, char **vp) { char *p = *vp, *q; u_char *d; while (p && *p) { struct tpcflags { char * name; u_char value; } flags[] = { { "syn", IP_FW_TCPF_SYN }, { "fin", IP_FW_TCPF_FIN }, { "ack", IP_FW_TCPF_ACK }, { "psh", IP_FW_TCPF_PSH }, { "rst", IP_FW_TCPF_RST }, { "urg", IP_FW_TCPF_URG } }; int i; if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; for (i = 0; i < sizeof(flags) / sizeof(flags[0]); ++i) if (!strncmp(p, flags[i].name, strlen(p))) { *d |= flags[i].value; break; } if (i == sizeof(flags) / sizeof(flags[0])) errx(EX_DATAERR, "invalid tcp flag ``%s''", p); p = q; } } static void fill_tcpopts(u_char *set, u_char *reset, char **vp) { char *p = *vp, *q; u_char *d; while (p && *p) { struct tpcopts { char * name; u_char value; } opts[] = { { "mss", IP_FW_TCPOPT_MSS }, { "window", IP_FW_TCPOPT_WINDOW }, { "sack", IP_FW_TCPOPT_SACK }, { "ts", IP_FW_TCPOPT_TS }, { "cc", IP_FW_TCPOPT_CC }, }; int i; if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; for (i = 0; i < sizeof(opts) / sizeof(opts[0]); ++i) if (!strncmp(p, opts[i].name, strlen(p))) { *d |= opts[i].value; break; } if (i == sizeof(opts) / sizeof(opts[0])) errx(EX_DATAERR, "invalid tcp option ``%s''", p); p = q; } } static void fill_ipopt(u_char *set, u_char *reset, char **vp) { char *p = *vp, *q; u_char *d; while (p && *p) { if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; if (!strncmp(p, "ssrr", strlen(p))) *d |= IP_FW_IPOPT_SSRR; if (!strncmp(p, "lsrr", strlen(p))) *d |= IP_FW_IPOPT_LSRR; if (!strncmp(p, "rr", strlen(p))) *d |= IP_FW_IPOPT_RR; if (!strncmp(p, "ts", strlen(p))) *d |= IP_FW_IPOPT_TS; p = q; } } static void fill_iptos(u_char *set, u_char *reset, char **vp) { char *p = *vp, *q; u_char *d; while (p && *p) { if (*p == '!') { p++; d = reset; } else { d = set; } q = strchr(p, ','); if (q) *q++ = '\0'; if (!strncmp(p, "lowdelay", strlen(p))) *d |= IPTOS_LOWDELAY; if (!strncmp(p, "throughput", strlen(p))) *d |= IPTOS_THROUGHPUT; if (!strncmp(p, "reliability", strlen(p))) *d |= IPTOS_RELIABILITY; if (!strncmp(p, "mincost", strlen(p))) *d |= IPTOS_MINCOST; if (!strncmp(p, "congestion", strlen(p))) *d |= IPTOS_CE; #if 0 /* conflicting! */ if (!strncmp(p, "ecntransport", strlen(p))) *d |= IPTOS_ECT; #endif p = q; } } 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; } } static void delete(int ac, char *av[]) { struct ip_fw rule; struct dn_pipe pipe; int i; int exitval = EX_OK; memset(&rule, 0, sizeof rule); 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(%s)", do_pipe == 1 ? pipe.pipe_nr: pipe.fs.fs_nr, "IP_DUMMYNET_DEL"); } } else { rule.fw_number = i; i = setsockopt(s, IPPROTO_IP, IP_FW_DEL, &rule, sizeof rule); if (i) { exitval = EX_UNAVAILABLE; warn("rule %u: setsockopt(IP_FW_DEL)", rule.fw_number); } } } if (exitval != EX_OK) exit(exitval); } static void verify_interface(union ip_fw_if *ifu) { struct ifreq ifr; /* * If a unit was specified, check for that exact interface. * If a wildcard was specified, check for unit 0. */ snprintf(ifr.ifr_name, sizeof(ifr.ifr_name), "%s%d", ifu->fu_via_if.name, ifu->fu_via_if.unit == -1 ? 0 : ifu->fu_via_if.unit); if (ioctl(s, SIOCGIFFLAGS, &ifr) < 0) warnx("warning: interface ``%s'' does not exist", ifr.ifr_name); } static void fill_iface(char *which, union ip_fw_if *ifu, int *byname, int ac, char *arg) { if (!ac) errx(EX_USAGE, "missing argument for ``%s''", which); /* Parse the interface or address */ if (!strcmp(arg, "any")) { ifu->fu_via_ip.s_addr = 0; *byname = 0; } else if (!isdigit(*arg)) { char *q; *byname = 1; strncpy(ifu->fu_via_if.name, arg, sizeof(ifu->fu_via_if.name)); ifu->fu_via_if.name[sizeof(ifu->fu_via_if.name) - 1] = '\0'; for (q = ifu->fu_via_if.name; *q && !isdigit(*q) && *q != '*'; q++) continue; ifu->fu_via_if.unit = (*q == '*') ? -1 : atoi(q); *q = '\0'; verify_interface(ifu); } else if (!inet_aton(arg, &ifu->fu_via_ip)) { errx(EX_DATAERR, "bad ip address ``%s''", arg); } else *byname = 0; } static void config_pipe(int ac, char **av) { struct dn_pipe pipe; int i; char *end; 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) { if (!strncmp(*av, "plr", strlen(*av))) { double d = strtod(av[1], NULL); if (d > 1) d = 1; else if (d < 0) d = 0; pipe.fs.plr = (int)(d*0x7fffffff); av += 2; ac -= 2; } else if (!strncmp(*av, "queue", strlen(*av))) { end = NULL; pipe.fs.qsize = strtoul(av[1], &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; } av += 2; ac -= 2; } else if (!strncmp(*av, "buckets", strlen(*av))) { pipe.fs.rq_size = strtoul(av[1], NULL, 0); av += 2; ac -= 2; } else if (!strncmp(*av, "mask", strlen(*av))) { /* per-flow queue, mask is dst_ip, dst_port, * src_ip, src_port, proto measured in bits */ u_int32_t a; void *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; av++; ac--; if (ac >= 1 && !strncmp(*av, "all", strlen(*av))) { /* special case -- all bits are 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; av++; ac--; continue; } while (ac >= 1) { int len = strlen(*av); if (!strncmp(*av, "dst-ip", len)) par = &pipe.fs.flow_mask.dst_ip; else if (!strncmp(*av, "src-ip", len)) par = &pipe.fs.flow_mask.src_ip; else if (!strncmp(*av, "dst-port", len)) par = &pipe.fs.flow_mask.dst_port; else if (!strncmp(*av, "src-port", len)) par = &pipe.fs.flow_mask.src_port; else if (!strncmp(*av, "proto", len)) par = &pipe.fs.flow_mask.proto; else break; if (ac < 2) errx(EX_USAGE, "mask: %s value" " missing", *av); if (*av[1] == '/') { a = strtoul(av[1]+1, &end, 0); if (a == 32) /* special case... */ a = ~0; else a = (1 << a) - 1; } else { a = strtoul(av[1], &end, 0); } if (par == &pipe.fs.flow_mask.src_port || par == &pipe.fs.flow_mask.dst_port) { if (a >= (1 << 16)) errx(EX_DATAERR, "mask: %s" " must be 16 bit, not" " 0x%08x", *av, a); *((u_int16_t *)par) = (u_int16_t)a; } else if (par == &pipe.fs.flow_mask.proto) { if (a >= (1 << 8)) errx(EX_DATAERR, "mask: %s" " must be" " 8 bit, not 0x%08x", *av, a); *((u_int8_t *)par) = (u_int8_t)a; } else *((u_int32_t *)par) = a; if (a != 0) pipe.fs.flags_fs |= DN_HAVE_FLOW_MASK; av += 2; ac -= 2; } /* end for */ } else if (!strncmp(*av, "red", strlen(*av)) || !strncmp(*av, "gred", strlen(*av))) { /* RED enabled */ pipe.fs.flags_fs |= DN_IS_RED; if (*av[0] == 'g') pipe.fs.flags_fs |= DN_IS_GENTLE_RED; if ((end = strsep(&av[1], "/"))) { double w_q = strtod(end, NULL); if (w_q > 1 || w_q <= 0) errx(EX_DATAERR, "w_q %f must be " "0 < x <= 1", w_q); pipe.fs.w_q = (int) (w_q * (1 << SCALE_RED)); } if ((end = strsep(&av[1], "/"))) { pipe.fs.min_th = strtoul(end, &end, 0); if (*end == 'K' || *end == 'k') pipe.fs.min_th *= 1024; } if ((end = strsep(&av[1], "/"))) { pipe.fs.max_th = strtoul(end, &end, 0); if (*end == 'K' || *end == 'k') pipe.fs.max_th *= 1024; } if ((end = strsep(&av[1], "/"))) { double max_p = strtod(end, NULL); if (max_p > 1 || max_p <= 0) errx(EX_DATAERR, "max_p %f must be " "0 < x <= 1", max_p); pipe.fs.max_p = (int)(max_p * (1 << SCALE_RED)); } av += 2; ac -= 2; } else if (!strncmp(*av, "droptail", strlen(*av))) { /* DROPTAIL */ pipe.fs.flags_fs &= ~(DN_IS_RED|DN_IS_GENTLE_RED); av += 1; ac -= 1; } else { int len = strlen(*av); if (do_pipe == 1) { /* some commands are only good for pipes. */ if (!strncmp(*av, "bw", len) || !strncmp(*av, "bandwidth", len)) { if (av[1][0] >= 'a' && av[1][0] <= 'z') { int l = sizeof(pipe.if_name)-1; /* interface name */ strncpy(pipe.if_name, av[1], l); pipe.if_name[l] = '\0'; pipe.bandwidth = 0; } else { pipe.if_name[0] = '\0'; pipe.bandwidth = strtoul(av[1], &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; } av += 2; ac -= 2; } else if (!strncmp(*av, "delay", len)) { pipe.delay = strtoul(av[1], NULL, 0); av += 2; ac -= 2; } else { errx(EX_DATAERR, "unrecognised pipe" " option ``%s''", *av); } } else { /* this refers to a queue */ if (!strncmp(*av, "weight", len)) { pipe.fs.weight = strtoul(av[1], &end, 0); av += 2; ac -= 2; } else if (!strncmp(*av, "pipe", len)) { pipe.fs.parent_nr = strtoul(av[1], &end, 0); av += 2; ac -= 2; } else { errx(EX_DATAERR, "unrecognised option " "``%s''", *av); } } } } if (do_pipe == 1) { if (pipe.pipe_nr == 0) errx(EX_DATAERR, "pipe_nr %d must be > 0", pipe.pipe_nr); if (pipe.delay > 10000) errx(EX_DATAERR, "delay %d must be < 10000", pipe.delay); } else { /* do_pipe == 2, queue */ if (pipe.fs.parent_nr == 0) errx(EX_DATAERR, "pipe %d must be > 0", pipe.fs.parent_nr); if (pipe.fs.weight >100) errx(EX_DATAERR, "weight %d must be <= 100", pipe.fs.weight); } if (pipe.fs.flags_fs & DN_QSIZE_IS_BYTES) { if (pipe.fs.qsize > 1024*1024) errx(EX_DATAERR, "queue size %d, must be < 1MB", pipe.fs.qsize); } else { if (pipe.fs.qsize > 100) errx(EX_DATAERR, "queue size %d, must be" " 2 <= x <= 100", pipe.fs.qsize); } if (pipe.fs.flags_fs & DN_IS_RED) { 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"); if (pipe.bandwidth) { size_t len; int lookup_depth, avg_pkt_size; double s, idle, weight, w_q; struct clockinfo clock; int t; 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 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" "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 */ 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)); } } #if 0 printf("configuring pipe %d bw %d delay %d size %d\n", pipe.pipe_nr, pipe.bandwidth, pipe.delay, pipe.queue_size); #endif i = setsockopt(s, IPPROTO_IP, IP_DUMMYNET_CONFIGURE, &pipe, sizeof pipe); if (i) err(1, "setsockopt(%s)", "IP_DUMMYNET_CONFIGURE"); } static void add(int ac, char *av[]) { struct ip_fw rule; int i; u_char proto; struct protoent *pe; int saw_xmrc = 0, saw_via = 0; memset(&rule, 0, sizeof rule); av++; ac--; /* Rule number */ if (ac && isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; } /* Action */ 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 */ rule.fw_flg |= IP_FW_F_RND_MATCH; /* we really store dont_match probability */ (long)rule.pipe_ptr = (long)((1 - d) * 0x7fffffff); } av += 2; ac -= 2; } if (ac == 0) errx(EX_USAGE, "missing action"); if (!strncmp(*av, "accept", strlen(*av)) || !strncmp(*av, "pass", strlen(*av)) || !strncmp(*av, "allow", strlen(*av)) || !strncmp(*av, "permit", strlen(*av))) { rule.fw_flg |= IP_FW_F_ACCEPT; av++; ac--; } else if (!strncmp(*av, "count", strlen(*av))) { rule.fw_flg |= IP_FW_F_COUNT; av++; ac--; } else if (!strncmp(*av, "pipe", strlen(*av))) { rule.fw_flg |= IP_FW_F_PIPE; av++; ac--; if (!ac) errx(EX_USAGE, "missing pipe number"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; } else if (!strncmp(*av, "queue", strlen(*av))) { rule.fw_flg |= IP_FW_F_QUEUE; av++; ac--; if (!ac) errx(EX_USAGE, "missing queue number"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; } else if (!strncmp(*av, "divert", strlen(*av))) { rule.fw_flg |= IP_FW_F_DIVERT; av++; ac--; if (!ac) errx(EX_USAGE, "missing %s port", "divert"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; if (rule.fw_divert_port == 0) { struct servent *s; setservent(1); s = getservbyname(av[-1], "divert"); if (s != NULL) rule.fw_divert_port = ntohs(s->s_port); else errx(EX_DATAERR, "illegal %s port", "divert"); } } else if (!strncmp(*av, "tee", strlen(*av))) { rule.fw_flg |= IP_FW_F_TEE; av++; ac--; if (!ac) errx(EX_USAGE, "missing %s port", "tee divert"); rule.fw_divert_port = strtoul(*av, NULL, 0); av++; ac--; if (rule.fw_divert_port == 0) { struct servent *s; setservent(1); s = getservbyname(av[-1], "divert"); if (s != NULL) rule.fw_divert_port = ntohs(s->s_port); else errx(EX_DATAERR, "illegal %s port", "tee divert"); } } else if (!strncmp(*av, "fwd", strlen(*av)) || !strncmp(*av, "forward", strlen(*av))) { struct in_addr dummyip; char *pp; rule.fw_flg |= IP_FW_F_FWD; av++; ac--; if (!ac) errx(EX_USAGE, "missing forwarding IP address"); rule.fw_fwd_ip.sin_len = sizeof(struct sockaddr_in); rule.fw_fwd_ip.sin_family = AF_INET; rule.fw_fwd_ip.sin_port = 0; pp = strchr(*av, ':'); if(pp == NULL) pp = strchr(*av, ','); if(pp != NULL) { *(pp++) = '\0'; i = lookup_port(pp, 0, 1, 0); if (i == -1) errx(EX_DATAERR, "illegal forwarding" " port ``%s''", pp); else rule.fw_fwd_ip.sin_port = (u_short)i; } fill_ip(&(rule.fw_fwd_ip.sin_addr), &dummyip, &ac, &av); if (rule.fw_fwd_ip.sin_addr.s_addr == 0) errx(EX_DATAERR, "illegal forwarding IP address"); } else if (!strncmp(*av, "skipto", strlen(*av))) { rule.fw_flg |= IP_FW_F_SKIPTO; av++; ac--; if (!ac) errx(EX_USAGE, "missing skipto rule number"); rule.fw_skipto_rule = strtoul(*av, NULL, 10); av++; ac--; } else if ((!strncmp(*av, "deny", strlen(*av)) || !strncmp(*av, "drop", strlen(*av)))) { rule.fw_flg |= IP_FW_F_DENY; av++; ac--; } 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 = IP_FW_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--; } else if (!strncmp(*av, "check-state", strlen(*av))) { rule.fw_flg |= IP_FW_F_CHECK_S; av++; ac--; goto done; } else { errx(EX_DATAERR, "invalid action ``%s''", *av); } /* [log] */ if (ac && !strncmp(*av, "log", strlen(*av))) { rule.fw_flg |= IP_FW_F_PRN; av++; ac--; } if (ac && !strncmp(*av, "logamount", strlen(*av))) { if (!(rule.fw_flg & IP_FW_F_PRN)) errx(EX_USAGE, "``logamount'' not valid without" " ``log''"); ac--; av++; if (!ac) errx(EX_USAGE, "``logamount'' requires argument"); rule.fw_logamount = atoi(*av); if (rule.fw_logamount < 0) errx(EX_DATAERR, "``logamount'' argument must be" " positive"); if (rule.fw_logamount == 0) rule.fw_logamount = -1; ac--; av++; } /* protocol */ if (ac == 0) errx(EX_USAGE, "missing protocol"); if ((proto = atoi(*av)) > 0) { rule.fw_prot = proto; av++; ac--; } else if (!strncmp(*av, "all", strlen(*av))) { rule.fw_prot = IPPROTO_IP; av++; ac--; } else if ((pe = getprotobyname(*av)) != NULL) { rule.fw_prot = pe->p_proto; av++; ac--; } else { errx(EX_DATAERR, "invalid protocol ``%s''", *av); } if (rule.fw_prot != IPPROTO_TCP && (rule.fw_flg & IP_FW_F_COMMAND) == IP_FW_F_REJECT && rule.fw_reject_code == IP_FW_REJECT_RST) errx(EX_DATAERR, "``reset'' is only valid for tcp packets"); /* from */ if (ac && !strncmp(*av, "from", strlen(*av))) { av++; ac--; } else errx(EX_USAGE, "missing ``from''"); if (ac && !strncmp(*av, "not", strlen(*av))) { rule.fw_flg |= IP_FW_F_INVSRC; av++; ac--; } if (!ac) errx(EX_USAGE, "missing arguments"); if (ac && !strncmp(*av, "me", strlen(*av))) { rule.fw_flg |= IP_FW_F_SME; av++; ac--; } else { fill_ip(&rule.fw_src, &rule.fw_smsk, &ac, &av); } if (ac && (isdigit(**av) || lookup_port(*av, rule.fw_prot, 1, 1) >= 0)) { u_short nports = 0; int retval; retval = fill_port(&nports, rule.fw_uar.fw_pts, 0, *av, rule.fw_prot); if (retval == 1) rule.fw_flg |= IP_FW_F_SRNG; else if (retval == 2) rule.fw_flg |= IP_FW_F_SMSK; IP_FW_SETNSRCP(&rule, nports); av++; ac--; } /* to */ if (ac && !strncmp(*av, "to", strlen(*av))) { av++; ac--; } else errx(EX_USAGE, "missing ``to''"); if (ac && !strncmp(*av, "not", strlen(*av))) { rule.fw_flg |= IP_FW_F_INVDST; av++; ac--; } if (!ac) errx(EX_USAGE, "missing arguments"); if (ac && !strncmp(*av, "me", strlen(*av))) { rule.fw_flg |= IP_FW_F_DME; av++; ac--; } else { fill_ip(&rule.fw_dst, &rule.fw_dmsk, &ac, &av); } if (ac && (isdigit(**av) || lookup_port(*av, rule.fw_prot, 1, 1) >= 0)) { u_short nports = 0; int retval; retval = fill_port(&nports, rule.fw_uar.fw_pts, IP_FW_GETNSRCP(&rule), *av, rule.fw_prot); if (retval == 1) rule.fw_flg |= IP_FW_F_DRNG; else if (retval == 2) rule.fw_flg |= IP_FW_F_DMSK; IP_FW_SETNDSTP(&rule, nports); av++; ac--; } if ((rule.fw_prot != IPPROTO_TCP) && (rule.fw_prot != IPPROTO_UDP) && (IP_FW_GETNSRCP(&rule) || IP_FW_GETNDSTP(&rule))) { errx(EX_USAGE, "only TCP and UDP protocols are valid" " with port specifications"); } while (ac) { if (!strncmp(*av, "uid", strlen(*av))) { struct passwd *pwd; char *end; uid_t uid; rule.fw_flg |= IP_FW_F_UID; ac--; av++; if (!ac) errx(EX_USAGE, "``uid'' requires argument"); uid = strtoul(*av, &end, 0); if (*end == '\0') pwd = getpwuid(uid); else pwd = getpwnam(*av); if (pwd == NULL) errx(EX_DATAERR, "uid \"%s\" is" " nonexistent", *av); rule.fw_uid = pwd->pw_uid; ac--; av++; } else if (!strncmp(*av, "gid", strlen(*av))) { struct group *grp; char *end; gid_t gid; rule.fw_flg |= IP_FW_F_GID; ac--; av++; if (!ac) errx(EX_USAGE, "``gid'' requires argument"); gid = strtoul(*av, &end, 0); if (*end == '\0') grp = getgrgid(gid); else grp = getgrnam(*av); if (grp == NULL) errx(EX_DATAERR, "gid \"%s\" is" " nonexistent", *av); rule.fw_gid = grp->gr_gid; ac--; av++; } else if (!strncmp(*av, "in", strlen(*av))) { rule.fw_flg |= IP_FW_F_IN; av++; ac--; } else if (!strncmp(*av, "keep-state", strlen(*av))) { u_long type; rule.fw_flg |= IP_FW_F_KEEP_S; av++; ac--; if (ac > 0 && (type = atoi(*av)) != 0) { (int)rule.next_rule_ptr = type; av++; ac--; } } else if (!strncmp(*av, "bridged", strlen(*av))) { rule.fw_flg |= IP_FW_BRIDGED; av++; ac--; } else if (!strncmp(*av, "out", strlen(*av))) { rule.fw_flg |= IP_FW_F_OUT; av++; ac--; } else if (ac && !strncmp(*av, "xmit", strlen(*av))) { union ip_fw_if ifu; int byname; if (saw_via) { badviacombo: errx(EX_USAGE, "``via'' is incompatible" " with ``xmit'' and ``recv''"); } saw_xmrc = 1; av++; ac--; fill_iface("xmit", &ifu, &byname, ac, *av); rule.fw_out_if = ifu; rule.fw_flg |= IP_FW_F_OIFACE; if (byname) rule.fw_flg |= IP_FW_F_OIFNAME; av++; ac--; } else if (ac && !strncmp(*av, "recv", strlen(*av))) { union ip_fw_if ifu; int byname; if (saw_via) goto badviacombo; saw_xmrc = 1; av++; ac--; fill_iface("recv", &ifu, &byname, ac, *av); rule.fw_in_if = ifu; rule.fw_flg |= IP_FW_F_IIFACE; if (byname) rule.fw_flg |= IP_FW_F_IIFNAME; av++; ac--; } else if (ac && !strncmp(*av, "via", strlen(*av))) { union ip_fw_if ifu; int byname = 0; if (saw_xmrc) goto badviacombo; saw_via = 1; av++; ac--; fill_iface("via", &ifu, &byname, ac, *av); rule.fw_out_if = rule.fw_in_if = ifu; if (byname) rule.fw_flg |= (IP_FW_F_IIFNAME | IP_FW_F_OIFNAME); av++; ac--; } else if (!strncmp(*av, "fragment", strlen(*av))) { rule.fw_flg |= IP_FW_F_FRAG; av++; ac--; } else if (!strncmp(*av, "ipoptions", strlen(*av)) || !strncmp(*av, "ipopts", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``ipoptions''"); rule.fw_ipflg |= IP_FW_IF_IPOPT; fill_ipopt(&rule.fw_ipopt, &rule.fw_ipnopt, av); av++; ac--; } else if (!strncmp(*av, "iplen", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``iplen''"); rule.fw_ipflg |= IP_FW_IF_IPLEN; rule.fw_iplen = (u_short)strtoul(*av, NULL, 0); av++; ac--; } else if (!strncmp(*av, "ipid", strlen(*av))) { unsigned long ipid; char *c; av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``ipid''"); ipid = strtoul(*av, &c, 0); if (*c != '\0') errx(EX_DATAERR, "argument to ipid must" " be numeric"); if (ipid > 65535) errx(EX_DATAERR, "argument to ipid out" " of range"); rule.fw_ipflg |= IP_FW_IF_IPID; rule.fw_ipid = (u_short)ipid; av++; ac--; } else if (!strncmp(*av, "iptos", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``iptos''"); rule.fw_ipflg |= IP_FW_IF_IPTOS; fill_iptos(&rule.fw_iptos, &rule.fw_ipntos, av); av++; ac--; } else if (!strncmp(*av, "ipttl", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``ipttl''"); rule.fw_ipflg |= IP_FW_IF_IPTTL; rule.fw_ipttl = (u_short)strtoul(*av, NULL, 0); av++; ac--; } else if (!strncmp(*av, "ipversion", strlen(*av)) || !strncmp(*av, "ipver", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``ipversion''"); rule.fw_ipflg |= IP_FW_IF_IPVER; rule.fw_ipver = (u_short)strtoul(*av, NULL, 0); av++; ac--; } else if (rule.fw_prot == IPPROTO_TCP) { if (!strncmp(*av, "established", strlen(*av))) { rule.fw_ipflg |= IP_FW_IF_TCPEST; av++; ac--; } else if (!strncmp(*av, "setup", strlen(*av))) { rule.fw_tcpf |= IP_FW_TCPF_SYN; rule.fw_tcpnf |= IP_FW_TCPF_ACK; rule.fw_ipflg |= IP_FW_IF_TCPFLG; av++; ac--; } else if (!strncmp(*av, "tcpflags", strlen(*av)) || !strncmp(*av, "tcpflgs", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``tcpflags''"); rule.fw_ipflg |= IP_FW_IF_TCPFLG; fill_tcpflag(&rule.fw_tcpf, &rule.fw_tcpnf, av); av++; ac--; } else if (!strncmp(*av, "tcpoptions", strlen(*av)) || !strncmp(*av, "tcpopts", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``tcpoptions''"); rule.fw_ipflg |= IP_FW_IF_TCPOPT; fill_tcpopts(&rule.fw_tcpopt, &rule.fw_tcpnopt, av); av++; ac--; } else if (!strncmp(*av, "tcpseq", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``tcpseq''"); rule.fw_ipflg |= IP_FW_IF_TCPSEQ; rule.fw_tcpseq = htonl(strtoul(*av, NULL, 0)); av++; ac--; } else if (!strncmp(*av, "tcpack", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``tcpack''"); rule.fw_ipflg |= IP_FW_IF_TCPACK; rule.fw_tcpack = htonl(strtoul(*av, NULL, 0)); av++; ac--; } else if (!strncmp(*av, "tcpwin", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``tcpwin''"); rule.fw_ipflg |= IP_FW_IF_TCPWIN; rule.fw_tcpwin = htons((u_short)strtoul(*av, NULL, 0)); av++; ac--; } else errx(EX_USAGE, "unknown or out of order" " argument ``%s''", *av); } else if (rule.fw_prot == IPPROTO_ICMP) { if (!strncmp(*av, "icmptypes", strlen(*av))) { av++; ac--; if (!ac) errx(EX_USAGE, "missing argument" " for ``icmptypes''"); fill_icmptypes(rule.fw_uar.fw_icmptypes, av, &rule.fw_flg); av++; ac--; } else errx(EX_USAGE, "unknown or out of" " order argument ``%s''", *av); } else errx(EX_USAGE, "unknown argument ``%s''", *av); } /* No direction specified -> do both directions */ if (!(rule.fw_flg & (IP_FW_F_OUT|IP_FW_F_IN))) rule.fw_flg |= (IP_FW_F_OUT|IP_FW_F_IN); /* Sanity check interface check, but handle "via" case separately */ if (saw_via) { if (rule.fw_flg & IP_FW_F_IN) rule.fw_flg |= IP_FW_F_IIFACE; if (rule.fw_flg & IP_FW_F_OUT) rule.fw_flg |= IP_FW_F_OIFACE; } else if ((rule.fw_flg & IP_FW_F_OIFACE) && (rule.fw_flg & IP_FW_F_IN)) { errx(EX_DATAERR, "can't check xmit interface of incoming" " packets"); } /* frag may not be used in conjunction with ports or TCP flags */ if (rule.fw_flg & IP_FW_F_FRAG) { if (rule.fw_tcpf || rule.fw_tcpnf) errx(EX_DATAERR, "can't mix 'frag' and tcpflags"); if (rule.fw_nports) errx(EX_DATAERR, "can't mix 'frag' and port" " specifications"); } if (rule.fw_flg & IP_FW_F_PRN) { if (!rule.fw_logamount) { size_t len = sizeof(int); if (sysctlbyname("net.inet.ip.fw.verbose_limit", &rule.fw_logamount, &len, NULL, 0) == -1) errx(1, "sysctlbyname(\"%s\")", "net.inet.ip.fw.verbose_limit"); } else if (rule.fw_logamount == -1) rule.fw_logamount = 0; rule.fw_loghighest = rule.fw_logamount; } done: i = sizeof(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[]) { struct ip_fw rule; 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; } memset(&rule, 0, sizeof rule); while (ac) { /* Rule number */ if (isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; if (setsockopt(s, IPPROTO_IP, IP_FW_ZERO, &rule, sizeof rule)) { warn("rule %u: setsockopt(IP_FW_ZERO)", rule.fw_number); failed = EX_UNAVAILABLE; } else if (!do_quiet) printf("Entry %d cleared\n", rule.fw_number); } else { errx(EX_USAGE, "invalid rule number ``%s''", *av); } } if (failed != EX_OK) exit(failed); } static void resetlog (int ac, char *av[]) { struct ip_fw rule; 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; } memset(&rule, 0, sizeof rule); while (ac) { /* Rule number */ if (isdigit(**av)) { rule.fw_number = atoi(*av); av++; ac--; if (setsockopt(s, IPPROTO_IP, IP_FW_RESETLOG, &rule, sizeof rule)) { warn("rule %u: setsockopt(IP_FW_RESETLOG)", rule.fw_number); failed = EX_UNAVAILABLE; } else if (!do_quiet) printf("Entry %d logging count reset\n", rule.fw_number); } else { errx(EX_DATAERR, "invalid rule number ``%s''", *av); } } if (failed != EX_OK) exit(failed); } 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, "s:adefNqtv")) != -1) switch (ch) { 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; if (*(av += optind) == NULL) errx(EX_USAGE, "bad arguments, for usage summary ``ipfw''"); if (!strncmp(*av, "pipe", strlen(*av))) { do_pipe = 1; ac--; av++; } else if (!strncmp(*av, "queue", strlen(*av))) { do_pipe = 2; ac--; av++; } if (!ac) errx(EX_USAGE, "pipe requires arguments"); /* allow argument swapping */ if (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))) { int do_flush = 0; if (do_force || do_quiet) do_flush = 1; else { int c; /* Ask the user */ printf("Are you sure? [yn] "); fflush(stdout); do { c = toupper(getc(stdin)); while (c != '\n' && getc(stdin) != '\n') if (feof(stdin)) return (0); } while (c != 'Y' && c != 'N'); printf("\n"); if (c == 'Y') do_flush = 1; } if (do_flush) { if (setsockopt(s, IPPROTO_IP, do_pipe ? IP_DUMMYNET_FLUSH : IP_FW_FLUSH, 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"); } } 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))) { list(--ac, ++av); } else if (!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 arguments, for usage summary ``ipfw''"); } return 0; } int main(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, c, lineno, qflag, pflag, status; FILE *f = NULL; pid_t preproc = 0; s = socket(AF_INET, SOCK_RAW, IPPROTO_RAW); if (s < 0) err(EX_UNAVAILABLE, "socket"); setbuf(stdout, 0); /* * Only interpret the last command line argument as a file to * be preprocessed if it is specified as an absolute pathname. */ if (ac > 1 && av[ac - 1][0] == '/' && access(av[ac - 1], R_OK) == 0) { qflag = pflag = i = 0; lineno = 0; 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)); } } else { ipfw_main(ac, av); } return EX_OK; } Index: head/sys/netinet/ip_fw.c =================================================================== --- head/sys/netinet/ip_fw.c (revision 83724) +++ head/sys/netinet/ip_fw.c (revision 83725) @@ -1,2088 +1,2102 @@ /* * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * Copyright (c) 1996 Alex Nash * Copyright (c) 2000 Luigi Rizzo * * 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. * * $FreeBSD$ */ #define DEB(x) #define DDB(x) x /* * Implement IP packet firewall */ #if !defined(KLD_MODULE) #include "opt_ipfw.h" #include "opt_ipdn.h" #include "opt_ipdivert.h" #include "opt_inet.h" #ifndef INET #error IPFIREWALL requires INET. #endif /* INET */ #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef DUMMYNET #include #endif #include #include #include #include #include #include #include /* XXX ethertype_ip */ static int fw_debug = 1; #ifdef IPFIREWALL_VERBOSE static int fw_verbose = 1; #else static int fw_verbose = 0; #endif int fw_one_pass = 1 ; #ifdef IPFIREWALL_VERBOSE_LIMIT static int fw_verbose_limit = IPFIREWALL_VERBOSE_LIMIT; #else static int fw_verbose_limit = 0; #endif static int fw_permanent_rules = 0; /* * Right now, two fields in the IP header are changed to host format * by the IP layer before calling the firewall. Ideally, we would like * to have them in network format so that the packet can be * used as it comes from the device driver (and is thus readonly). */ static u_int64_t counter; /* counter for ipfw_report(NULL...) */ struct ipfw_flow_id last_pkt ; #define IPFW_DEFAULT_RULE ((u_int)(u_short)~0) LIST_HEAD (ip_fw_head, ip_fw_chain) ip_fw_chain_head; MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's"); #ifdef SYSCTL_NODE SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, enable, CTLFLAG_RW, &fw_enable, 0, "Enable ipfw"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO,one_pass,CTLFLAG_RW, &fw_one_pass, 0, "Only do a single pass through ipfw when using dummynet(4)"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, debug, CTLFLAG_RW, &fw_debug, 0, "Enable printing of debug ip_fw statements"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose, CTLFLAG_RW, &fw_verbose, 0, "Log matches to ipfw rules"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, verbose_limit, CTLFLAG_RW, &fw_verbose_limit, 0, "Set upper limit of matches of ipfw rules logged"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, permanent_rules, CTLFLAG_RW, &fw_permanent_rules, 0, "Set rule number, below which rules are permanent"); /* * Extension for stateful ipfw. * * Dynamic rules are stored in lists accessed through a hash table * (ipfw_dyn_v) whose size is curr_dyn_buckets. This value can * be modified through the sysctl variable dyn_buckets which is * updated when the table becomes empty. * * XXX currently there is only one list, ipfw_dyn. * * When a packet is received, it is first hashed, then matched * against the entries in the corresponding list. * Matching occurs according to the rule type. The default is to * match the four fields and the protocol, and rules are bidirectional. * * For a busy proxy/web server we will have lots of connections to * the server. We could decide for a rule type where we ignore * ports (different hashing) and avoid special SYN/RST/FIN handling. * * XXX when we decide to support more than one rule type, we should * repeat the hashing multiple times uing only the useful fields. * Or, we could run the various tests in parallel, because the * 'move to front' technique should shorten the average search. * * The lifetime of dynamic rules is regulated by dyn_*_lifetime, * measured in seconds and depending on the flags. * * The total number of dynamic rules is stored in dyn_count. * The max number of dynamic rules is dyn_max. When we reach * the maximum number of rules we do not create anymore. This is * done to avoid consuming too much memory, but also too much * time when searching on each packet (ideally, we should try instead * to put a limit on the length of the list on each bucket...). * * Each dynamic rules holds a pointer to the parent ipfw rule so * we know what action to perform. Dynamic rules are removed when * the parent rule is deleted. * There are some limitations with dynamic rules -- we do not * obey the 'randomized match', and we do not do multiple * passes through the firewall. * XXX check the latter!!! */ static struct ipfw_dyn_rule **ipfw_dyn_v = NULL ; static u_int32_t dyn_buckets = 256 ; /* must be power of 2 */ static u_int32_t curr_dyn_buckets = 256 ; /* must be power of 2 */ +/** + * timeouts for various events in handing dynamic rules. + */ static u_int32_t dyn_ack_lifetime = 300 ; static u_int32_t dyn_syn_lifetime = 20 ; -static u_int32_t dyn_fin_lifetime = 20 ; -static u_int32_t dyn_rst_lifetime = 5 ; -static u_int32_t dyn_short_lifetime = 30 ; -static u_int32_t dyn_count = 0 ; -static u_int32_t dyn_max = 1000 ; +static u_int32_t dyn_fin_lifetime = 1 ; +static u_int32_t dyn_rst_lifetime = 1 ; +static u_int32_t dyn_udp_lifetime = 10 ; +static u_int32_t dyn_short_lifetime = 5 ; + +/* + * after reaching 0, dynamic rules are considered still valid for + * an additional grace time, unless there is lack of resources. + */ +static u_int32_t dyn_grace_time = 10 ; + +static u_int32_t static_count = 0 ; /* # of static rules */ +static u_int32_t dyn_count = 0 ; /* # of dynamic rules */ +static u_int32_t dyn_max = 1000 ; /* max # of dynamic rules */ + SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_buckets, CTLFLAG_RW, &dyn_buckets, 0, "Number of dyn. buckets"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, curr_dyn_buckets, CTLFLAG_RD, &curr_dyn_buckets, 0, "Current Number of dyn. buckets"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_count, CTLFLAG_RD, &dyn_count, 0, "Number of dyn. rules"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_max, CTLFLAG_RW, &dyn_max, 0, "Max number of dyn. rules"); +SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, static_count, CTLFLAG_RD, + &static_count, 0, "Number of static rules"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_ack_lifetime, CTLFLAG_RW, &dyn_ack_lifetime, 0, "Lifetime of dyn. rules for acks"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_syn_lifetime, CTLFLAG_RW, &dyn_syn_lifetime, 0, "Lifetime of dyn. rules for syn"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_fin_lifetime, CTLFLAG_RW, &dyn_fin_lifetime, 0, "Lifetime of dyn. rules for fin"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_rst_lifetime, CTLFLAG_RW, &dyn_rst_lifetime, 0, "Lifetime of dyn. rules for rst"); +SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_udp_lifetime, CTLFLAG_RW, + &dyn_udp_lifetime, 0, "Lifetime of dyn. rules for UDP"); SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_short_lifetime, CTLFLAG_RW, &dyn_short_lifetime, 0, "Lifetime of dyn. rules for other situations"); +SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_grace_time, CTLFLAG_RD, + &dyn_grace_time, 0, "Grace time for dyn. rules"); + #endif #define dprintf(a) do { \ if (fw_debug) \ printf a; \ } while (0) #define SNPARGS(buf, len) buf + len, sizeof(buf) > len ? sizeof(buf) - len : 0 static int add_entry __P((struct ip_fw_head *chainptr, struct ip_fw *frwl)); static int del_entry __P((struct ip_fw_head *chainptr, u_short number)); -static int zero_entry __P((struct ip_fw *)); -static int resetlog_entry __P((struct ip_fw *)); +static int zero_entry __P((struct ip_fw *, int)); static int check_ipfw_struct __P((struct ip_fw *m)); static __inline int iface_match __P((struct ifnet *ifp, union ip_fw_if *ifu, int byname)); static int ipopts_match __P((struct ip *ip, struct ip_fw *f)); static int iptos_match __P((struct ip *ip, struct ip_fw *f)); static __inline int port_match __P((u_short *portptr, int nports, u_short port, int range_flag, int mask)); static int tcpflg_match __P((struct tcphdr *tcp, struct ip_fw *f)); static int icmptype_match __P((struct icmp * icmp, struct ip_fw * f)); static void ipfw_report __P((struct ip_fw *f, struct ip *ip, int offset, int ip_len, struct ifnet *rif, struct ifnet *oif)); static void flush_rule_ptrs(void); static int ip_fw_chk __P((struct ip **pip, int hlen, struct ifnet *oif, u_int16_t *cookie, struct mbuf **m, struct ip_fw_chain **flow_id, struct sockaddr_in **next_hop)); static int ip_fw_ctl __P((struct sockopt *sopt)); static char err_prefix[] = "ip_fw_ctl:"; /* * Returns 1 if the port is matched by the vector, 0 otherwise */ static __inline int port_match(u_short *portptr, int nports, u_short port, int range_flag, int mask) { if (!nports) return 1; if (mask) { if ( 0 == ((portptr[0] ^ port) & portptr[1]) ) return 1; nports -= 2; portptr += 2; } if (range_flag) { if (portptr[0] <= port && port <= portptr[1]) { return 1; } nports -= 2; portptr += 2; } while (nports-- > 0) { if (*portptr++ == port) { return 1; } } return 0; } static int tcpflg_match(struct tcphdr *tcp, struct ip_fw *f) { u_char flg_set, flg_clr; /* * If an established connection is required, reject packets that * have only SYN of RST|ACK|SYN set. Otherwise, fall through to * other flag requirements. */ if ((f->fw_ipflg & IP_FW_IF_TCPEST) && ((tcp->th_flags & (IP_FW_TCPF_RST | IP_FW_TCPF_ACK | IP_FW_TCPF_SYN)) == IP_FW_TCPF_SYN)) return 0; flg_set = tcp->th_flags & f->fw_tcpf; flg_clr = tcp->th_flags & f->fw_tcpnf; if (flg_set != f->fw_tcpf) return 0; if (flg_clr) return 0; return 1; } static int icmptype_match(struct icmp *icmp, struct ip_fw *f) { int type; if (!(f->fw_flg & IP_FW_F_ICMPBIT)) return(1); type = icmp->icmp_type; /* check for matching type in the bitmap */ if (type < IP_FW_ICMPTYPES_MAX && (f->fw_uar.fw_icmptypes[type / (sizeof(unsigned) * NBBY)] & (1U << (type % (sizeof(unsigned) * NBBY))))) return(1); return(0); /* no match */ } static int is_icmp_query(struct ip *ip) { const struct icmp *icmp; int icmp_type; icmp = (struct icmp *)((u_int32_t *)ip + ip->ip_hl); icmp_type = icmp->icmp_type; if (icmp_type == ICMP_ECHO || icmp_type == ICMP_ROUTERSOLICIT || icmp_type == ICMP_TSTAMP || icmp_type == ICMP_IREQ || icmp_type == ICMP_MASKREQ) return(1); return(0); } static int ipopts_match(struct ip *ip, struct ip_fw *f) { register u_char *cp; int opt, optlen, cnt; u_char opts, nopts, nopts_sve; cp = (u_char *)(ip + 1); cnt = (ip->ip_hl << 2) - sizeof (struct ip); opts = f->fw_ipopt; nopts = nopts_sve = f->fw_ipnopt; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[IPOPT_OPTVAL]; if (opt == IPOPT_EOL) break; if (opt == IPOPT_NOP) optlen = 1; else { optlen = cp[IPOPT_OLEN]; if (optlen <= 0 || optlen > cnt) { return 0; /*XXX*/ } } switch (opt) { default: break; case IPOPT_LSRR: opts &= ~IP_FW_IPOPT_LSRR; nopts &= ~IP_FW_IPOPT_LSRR; break; case IPOPT_SSRR: opts &= ~IP_FW_IPOPT_SSRR; nopts &= ~IP_FW_IPOPT_SSRR; break; case IPOPT_RR: opts &= ~IP_FW_IPOPT_RR; nopts &= ~IP_FW_IPOPT_RR; break; case IPOPT_TS: opts &= ~IP_FW_IPOPT_TS; nopts &= ~IP_FW_IPOPT_TS; break; } if (opts == nopts) break; } if (opts == 0 && nopts == nopts_sve) return 1; else return 0; } static int iptos_match(struct ip *ip, struct ip_fw *f) { u_int flags = (ip->ip_tos & 0x1f); u_char opts, nopts, nopts_sve; opts = f->fw_iptos; nopts = nopts_sve = f->fw_ipntos; while (flags != 0) { u_int flag; flag = 1 << (ffs(flags) -1); opts &= ~flag; nopts &= ~flag; flags &= ~flag; } if (opts == 0 && nopts == nopts_sve) return 1; else return 0; } static int tcpopts_match(struct tcphdr *tcp, struct ip_fw *f) { register u_char *cp; int opt, optlen, cnt; u_char opts, nopts, nopts_sve; cp = (u_char *)(tcp + 1); cnt = (tcp->th_off << 2) - sizeof (struct tcphdr); opts = f->fw_tcpopt; nopts = nopts_sve = f->fw_tcpnopt; for (; cnt > 0; cnt -= optlen, cp += optlen) { opt = cp[0]; if (opt == TCPOPT_EOL) break; if (opt == TCPOPT_NOP) optlen = 1; else { optlen = cp[1]; if (optlen <= 0) break; } switch (opt) { default: break; case TCPOPT_MAXSEG: opts &= ~IP_FW_TCPOPT_MSS; nopts &= ~IP_FW_TCPOPT_MSS; break; case TCPOPT_WINDOW: opts &= ~IP_FW_TCPOPT_WINDOW; nopts &= ~IP_FW_TCPOPT_WINDOW; break; case TCPOPT_SACK_PERMITTED: case TCPOPT_SACK: opts &= ~IP_FW_TCPOPT_SACK; nopts &= ~IP_FW_TCPOPT_SACK; break; case TCPOPT_TIMESTAMP: opts &= ~IP_FW_TCPOPT_TS; nopts &= ~IP_FW_TCPOPT_TS; break; case TCPOPT_CC: case TCPOPT_CCNEW: case TCPOPT_CCECHO: opts &= ~IP_FW_TCPOPT_CC; nopts &= ~IP_FW_TCPOPT_CC; break; } if (opts == nopts) break; } if (opts == 0 && nopts == nopts_sve) return 1; else return 0; } static __inline int iface_match(struct ifnet *ifp, union ip_fw_if *ifu, int byname) { /* Check by name or by IP address */ if (byname) { /* Check unit number (-1 is wildcard) */ if (ifu->fu_via_if.unit != -1 && ifp->if_unit != ifu->fu_via_if.unit) return(0); /* Check name */ if (strncmp(ifp->if_name, ifu->fu_via_if.name, FW_IFNLEN)) return(0); return(1); } else if (ifu->fu_via_ip.s_addr != 0) { /* Zero == wildcard */ struct ifaddr *ia; TAILQ_FOREACH(ia, &ifp->if_addrhead, ifa_link) { if (ia->ifa_addr == NULL) continue; if (ia->ifa_addr->sa_family != AF_INET) continue; if (ifu->fu_via_ip.s_addr != ((struct sockaddr_in *) (ia->ifa_addr))->sin_addr.s_addr) continue; return(1); } return(0); } return(1); } static void ipfw_report(struct ip_fw *f, struct ip *ip, int offset, int ip_len, struct ifnet *rif, struct ifnet *oif) { struct tcphdr *const tcp = (struct tcphdr *) ((u_int32_t *) ip+ ip->ip_hl); struct udphdr *const udp = (struct udphdr *) ((u_int32_t *) ip+ ip->ip_hl); struct icmp *const icmp = (struct icmp *) ((u_int32_t *) ip + ip->ip_hl); u_int64_t count; char *action; char action2[32], proto[47], name[18], fragment[27]; int len; count = f ? f->fw_pcnt : ++counter; if ((f == NULL && fw_verbose_limit != 0 && count > fw_verbose_limit) || (f && f->fw_logamount != 0 && count > f->fw_loghighest)) return; /* Print command name */ snprintf(SNPARGS(name, 0), "ipfw: %d", f ? f->fw_number : -1); action = action2; if (!f) action = "Refuse"; else { switch (f->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_DENY: action = "Deny"; break; case IP_FW_F_REJECT: if (f->fw_reject_code == IP_FW_REJECT_RST) action = "Reset"; else action = "Unreach"; break; case IP_FW_F_ACCEPT: action = "Accept"; break; case IP_FW_F_COUNT: action = "Count"; break; #ifdef IPDIVERT case IP_FW_F_DIVERT: snprintf(SNPARGS(action2, 0), "Divert %d", f->fw_divert_port); break; case IP_FW_F_TEE: snprintf(SNPARGS(action2, 0), "Tee %d", f->fw_divert_port); break; #endif case IP_FW_F_SKIPTO: snprintf(SNPARGS(action2, 0), "SkipTo %d", f->fw_skipto_rule); break; #ifdef DUMMYNET case IP_FW_F_PIPE: snprintf(SNPARGS(action2, 0), "Pipe %d", f->fw_skipto_rule); break; case IP_FW_F_QUEUE: snprintf(SNPARGS(action2, 0), "Queue %d", f->fw_skipto_rule); break; #endif #ifdef IPFIREWALL_FORWARD case IP_FW_F_FWD: if (f->fw_fwd_ip.sin_port) snprintf(SNPARGS(action2, 0), "Forward to %s:%d", inet_ntoa(f->fw_fwd_ip.sin_addr), f->fw_fwd_ip.sin_port); else snprintf(SNPARGS(action2, 0), "Forward to %s", inet_ntoa(f->fw_fwd_ip.sin_addr)); break; #endif default: action = "UNKNOWN"; break; } } switch (ip->ip_p) { case IPPROTO_TCP: len = snprintf(SNPARGS(proto, 0), "TCP %s", inet_ntoa(ip->ip_src)); if (offset == 0) len += snprintf(SNPARGS(proto, len), ":%d ", ntohs(tcp->th_sport)); else len += snprintf(SNPARGS(proto, len), " "); len += snprintf(SNPARGS(proto, len), "%s", inet_ntoa(ip->ip_dst)); if (offset == 0) snprintf(SNPARGS(proto, len), ":%d", ntohs(tcp->th_dport)); break; case IPPROTO_UDP: len = snprintf(SNPARGS(proto, 0), "UDP %s", inet_ntoa(ip->ip_src)); if (offset == 0) len += snprintf(SNPARGS(proto, len), ":%d ", ntohs(udp->uh_sport)); else len += snprintf(SNPARGS(proto, len), " "); len += snprintf(SNPARGS(proto, len), "%s", inet_ntoa(ip->ip_dst)); if (offset == 0) snprintf(SNPARGS(proto, len), ":%d", ntohs(udp->uh_dport)); break; case IPPROTO_ICMP: if (offset == 0) len = snprintf(SNPARGS(proto, 0), "ICMP:%u.%u ", icmp->icmp_type, icmp->icmp_code); else len = snprintf(SNPARGS(proto, 0), "ICMP "); len += snprintf(SNPARGS(proto, len), "%s", inet_ntoa(ip->ip_src)); snprintf(SNPARGS(proto, len), " %s", inet_ntoa(ip->ip_dst)); break; default: len = snprintf(SNPARGS(proto, 0), "P:%d %s", ip->ip_p, inet_ntoa(ip->ip_src)); snprintf(SNPARGS(proto, len), " %s", inet_ntoa(ip->ip_dst)); break; } if (ip->ip_off & (IP_MF | IP_OFFMASK)) snprintf(SNPARGS(fragment, 0), " (frag %d:%d@%d%s)", ntohs(ip->ip_id), ip_len - (ip->ip_hl << 2), offset << 3, (ip->ip_off & IP_MF) ? "+" : ""); else fragment[0] = '\0'; if (oif) log(LOG_SECURITY | LOG_INFO, "%s %s %s out via %s%d%s\n", name, action, proto, oif->if_name, oif->if_unit, fragment); else if (rif) log(LOG_SECURITY | LOG_INFO, "%s %s %s in via %s%d%s\n", name, action, proto, rif->if_name, rif->if_unit, fragment); else log(LOG_SECURITY | LOG_INFO, "%s %s %s%s\n", name, action, proto, fragment); if ((f ? f->fw_logamount != 0 : 1) && count == (f ? f->fw_loghighest : fw_verbose_limit)) log(LOG_SECURITY | LOG_NOTICE, "ipfw: limit %d reached on entry %d\n", f ? f->fw_logamount : fw_verbose_limit, f ? f->fw_number : -1); } static __inline int hash_packet(struct ipfw_flow_id *id) { u_int32_t i ; i = (id->dst_ip) ^ (id->src_ip) ^ (id->dst_port) ^ (id->src_port); i &= (curr_dyn_buckets - 1) ; return i ; } +/** + * unlink a dynamic rule from a chain. prev is a pointer to + * the previous one, q is a pointer to the rule to delete, + * head is a pointer to the head of the queue. + * Modifies q and potentially also head. + */ +#define UNLINK_DYN_RULE(prev, head, q) { \ + struct ipfw_dyn_rule *old_q = q; \ + \ + DEB(printf("-- unlink 0x%08x %d -> 0x%08x %d, %d left\n", \ + (q->id.src_ip), (q->id.src_port), \ + (q->id.dst_ip), (q->id.dst_port), dyn_count-1 ); ) \ + if (prev != NULL) \ + prev->next = q = q->next ; \ + else \ + ipfw_dyn_v[i] = q = q->next ; \ + dyn_count-- ; \ + free(old_q, M_IPFW); } + + #define TIME_LEQ(a,b) ((int)((a)-(b)) <= 0) -/* +/** * Remove all dynamic rules pointing to a given chain, or all * rules if chain == NULL. Second parameter is 1 if we want to * delete unconditionally, otherwise only expired rules are removed. */ static void remove_dyn_rule(struct ip_fw_chain *chain, int force) { - struct ipfw_dyn_rule *prev, *q, *old_q ; + struct ipfw_dyn_rule *prev, *q; int i ; static u_int32_t last_remove = 0 ; if (ipfw_dyn_v == NULL || dyn_count == 0) return ; /* do not expire more than once per second, it is useless */ if (force == 0 && last_remove == time_second) return ; last_remove = time_second ; for (i = 0 ; i < curr_dyn_buckets ; i++) { for (prev=NULL, q = ipfw_dyn_v[i] ; q ; ) { - if ( (chain == NULL || chain == q->chain) && - (force || TIME_LEQ( q->expire , time_second ) ) ) { - DEB(printf("-- remove entry 0x%08x %d -> 0x%08x %d, %d left\n", - (q->id.src_ip), (q->id.src_port), - (q->id.dst_ip), (q->id.dst_port), dyn_count-1 ); ) - old_q = q ; - if (prev != NULL) - prev->next = q = q->next ; - else - ipfw_dyn_v[i] = q = q->next ; - dyn_count-- ; - free(old_q, M_IPFW); - continue ; + /* + * logic can become complex here, so we split tests. + * First, test if we match any chain, + * then make sure the rule is expired or we want to kill it, + * and possibly more in the future. + */ + int zap = ( chain == NULL || chain == q->chain); + if (zap) + zap = force || TIME_LEQ( q->expire , time_second ); + + if (zap) { + UNLINK_DYN_RULE(prev, ipfw_dyn_v[i], q); } else { prev = q ; q = q->next ; } } } } +#define EXPIRE_DYN_CHAIN(chain) remove_dyn_rule(chain, 0 /* expired ones */) +#define EXPIRE_DYN_CHAINS() remove_dyn_rule(NULL, 0 /* expired ones */) +#define DELETE_DYN_CHAIN(chain) remove_dyn_rule(chain, 1 /* force removal */) +#define DELETE_DYN_CHAINS() remove_dyn_rule(NULL, 1 /* force removal */) + +/** + * lookup a dynamic rule. + */ static struct ipfw_dyn_rule * lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction) { /* * stateful ipfw extensions. * Lookup into dynamic session queue */ - struct ipfw_dyn_rule *prev, *q, *old_q ; + struct ipfw_dyn_rule *prev, *q ; int i, dir = 0; #define MATCH_FORWARD 1 if (ipfw_dyn_v == NULL) return NULL ; i = hash_packet( pkt ); for (prev=NULL, q = ipfw_dyn_v[i] ; q != NULL ; ) { if (TIME_LEQ( q->expire , time_second ) ) { /* expire entry */ - old_q = q ; - if (prev != NULL) - prev->next = q = q->next ; - else - ipfw_dyn_v[i] = q = q->next ; - dyn_count-- ; - free(old_q, M_IPFW); + UNLINK_DYN_RULE(prev, ipfw_dyn_v[i], q); continue ; } if ( pkt->proto == q->id.proto) { - switch (q->type) { - default: /* bidirectional rule, no masks */ - if (pkt->src_ip == q->id.src_ip && - pkt->dst_ip == q->id.dst_ip && - pkt->src_port == q->id.src_port && - pkt->dst_port == q->id.dst_port ) { - dir = MATCH_FORWARD ; - goto found ; - } - if (pkt->src_ip == q->id.dst_ip && - pkt->dst_ip == q->id.src_ip && - pkt->src_port == q->id.dst_port && - pkt->dst_port == q->id.src_port ) { - dir = 0 ; /* reverse match */ - goto found ; - } - break ; + if (pkt->src_ip == q->id.src_ip && + pkt->dst_ip == q->id.dst_ip && + pkt->src_port == q->id.src_port && + pkt->dst_port == q->id.dst_port ) { + dir = MATCH_FORWARD ; + goto found ; } + if (pkt->src_ip == q->id.dst_ip && + pkt->dst_ip == q->id.src_ip && + pkt->src_port == q->id.dst_port && + pkt->dst_port == q->id.src_port ) { + dir = 0 ; /* reverse match */ + goto found ; + } } prev = q ; q = q->next ; } return NULL ; /* clearly not found */ found: if ( prev != NULL) { /* found and not in front */ prev->next = q->next ; q->next = ipfw_dyn_v[i] ; ipfw_dyn_v[i] = q ; } if (pkt->proto == IPPROTO_TCP) { /* update state according to flags */ u_char flags = pkt->flags & (TH_FIN|TH_SYN|TH_RST); q->state |= (dir == MATCH_FORWARD ) ? flags : (flags << 8); switch (q->state) { case TH_SYN : /* opening */ q->expire = time_second + dyn_syn_lifetime ; break ; case TH_SYN | (TH_SYN << 8) : /* move to established */ q->expire = time_second + dyn_ack_lifetime ; break ; case TH_SYN | (TH_SYN << 8) | TH_FIN : case TH_SYN | (TH_SYN << 8) | (TH_FIN << 8) : /* one side tries to close */ q->expire = time_second + dyn_ack_lifetime ; break ; case TH_SYN | (TH_SYN << 8) | TH_FIN | (TH_FIN << 8) : /* both sides closed */ q->expire = time_second + dyn_fin_lifetime ; break ; default: #if 0 /* * reset or some invalid combination, but can also * occur if we use keep-state the wrong way. */ if ( (q->state & ((TH_RST << 8)|TH_RST)) == 0) printf("invalid state: 0x%x\n", q->state); #endif q->expire = time_second + dyn_rst_lifetime ; break ; } + } else if (pkt->proto == IPPROTO_UDP) { + q->expire = time_second + dyn_udp_lifetime ; } else { - /* should do something for UDP and others... */ + /* other protocols */ q->expire = time_second + dyn_short_lifetime ; } if (match_direction) *match_direction = dir ; return q ; } /* * Install state for a dynamic session. */ -static void -add_dyn_rule(struct ipfw_flow_id *id, struct ipfw_flow_id *mask, +static struct ipfw_dyn_rule * +add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw_chain *chain) { struct ipfw_dyn_rule *r ; int i ; if (ipfw_dyn_v == NULL || - (dyn_count == 0 && dyn_buckets != curr_dyn_buckets)) { - /* try reallocation, make sure we have a power of 2 */ - u_int32_t i = dyn_buckets ; - while ( i > 0 && (i & 1) == 0 ) - i >>= 1 ; - if (i != 1) /* not a power of 2 */ - dyn_buckets = curr_dyn_buckets ; /* reset */ - else { - if (ipfw_dyn_v != NULL) + (dyn_count == 0 && dyn_buckets != curr_dyn_buckets)) { + /* try reallocation, make sure we have a power of 2 */ + u_int32_t i = dyn_buckets ; + while ( i > 0 && (i & 1) == 0 ) + i >>= 1 ; + if (i != 1) /* not a power of 2 */ + dyn_buckets = curr_dyn_buckets ; /* reset */ + else { + curr_dyn_buckets = dyn_buckets ; + if (ipfw_dyn_v != NULL) free(ipfw_dyn_v, M_IPFW); - ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof r, + ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof r, M_IPFW, M_DONTWAIT | M_ZERO); - if (ipfw_dyn_v == NULL) - return ; /* failed ! */ - } + if (ipfw_dyn_v == NULL) + return NULL; /* failed ! */ + } } i = hash_packet(id); r = malloc(sizeof *r, M_IPFW, M_DONTWAIT | M_ZERO); if (r == NULL) { printf ("sorry cannot allocate state\n"); - return ; + return NULL ; } - if (mask) - r->mask = *mask ; r->id = *id ; r->expire = time_second + dyn_syn_lifetime ; r->chain = chain ; - r->type = ((struct ip_fw_ext *)chain->rule)->dyn_type ; + r->dyn_type = dyn_type ; + r->pcnt = r->bcnt = 0 ; r->bucket = i ; r->next = ipfw_dyn_v[i] ; ipfw_dyn_v[i] = r ; dyn_count++ ; DEB(printf("-- add entry 0x%08x %d -> 0x%08x %d, %d left\n", (r->id.src_ip), (r->id.src_port), (r->id.dst_ip), (r->id.dst_port), dyn_count ); ) + return r; } /* * Install dynamic state. * There are different types of dynamic rules which can be installed. * The type is in chain->dyn_type. * Type 0 (default) is a bidirectional rule + * + * Returns 1 (failure) if state is not installed. */ -static void +static int install_state(struct ip_fw_chain *chain) { struct ipfw_dyn_rule *q ; static int last_log ; - u_long type = ((struct ip_fw_ext *)chain->rule)->dyn_type ; + u_int8_t type = ((struct ip_fw_ext *)chain->rule)->dyn_type ; DEB(printf("-- install state type %d 0x%08lx %u -> 0x%08lx %u\n", type, (last_pkt.src_ip), (last_pkt.src_port), (last_pkt.dst_ip), (last_pkt.dst_port) );) q = lookup_dyn_rule(&last_pkt, NULL) ; - if (q != NULL) { - if (last_log == time_second) - return ; - last_log = time_second ; - printf(" entry already present, done\n"); - return ; + if (q != NULL) { /* should never occur */ + if (last_log != time_second) { + last_log = time_second ; + printf(" entry already present, done\n"); + } + return 0 ; } if (dyn_count >= dyn_max) /* try remove old ones... */ - remove_dyn_rule(NULL, 0 /* expire */); + EXPIRE_DYN_CHAINS(); if (dyn_count >= dyn_max) { - if (last_log == time_second) - return ; - last_log = time_second ; - printf(" Too many dynamic rules, sorry\n"); - return ; + if (last_log != time_second) { + last_log = time_second ; + printf(" Too many dynamic rules, sorry\n"); + } + return 1; /* cannot install, notify caller */ } switch (type) { default: /* bidir rule */ - add_dyn_rule(&last_pkt, NULL, chain); + add_dyn_rule(&last_pkt, DYN_KEEP_STATE, chain); break ; } - q = lookup_dyn_rule(&last_pkt, NULL) ; /* XXX this just sets the lifetime ... */ + lookup_dyn_rule(&last_pkt, NULL) ; /* XXX just set the lifetime */ + return 0; } /* * given an ip_fw_chain *, lookup_next_rule will return a pointer * of the same type to the next one. This can be either the jump * target (for skipto instructions) or the next one in the chain (in * all other cases including a missing jump target). * Backward jumps are not allowed, so start looking from the next * rule... */ static struct ip_fw_chain * lookup_next_rule(struct ip_fw_chain *me); static struct ip_fw_chain * lookup_next_rule(struct ip_fw_chain *me) { struct ip_fw_chain *chain ; int rule = me->rule->fw_skipto_rule ; /* guess... */ if ( (me->rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_SKIPTO ) for (chain = LIST_NEXT(me,next); chain ; chain = LIST_NEXT(chain,next)) if (chain->rule->fw_number >= rule) return chain ; return LIST_NEXT(me,next) ; /* failure or not a skipto */ } /* * Parameters: * * pip Pointer to packet header (struct ip **) * hlen Packet header length * oif Outgoing interface, or NULL if packet is incoming * *cookie Skip up to the first rule past this rule number; * upon return, non-zero port number for divert or tee. * Special case: cookie == NULL on input for bridging. * *m The packet; we set to NULL when/if we nuke it. * *flow_id pointer to the last matching rule (in/out) * *next_hop socket we are forwarding to (in/out). * * Return value: * * IP_FW_PORT_DENY_FLAG the packet must be dropped. * 0 The packet is to be accepted and routed normally OR * the packet was denied/rejected and has been dropped; * in the latter case, *m is equal to NULL upon return. * port Divert the packet to port, with these caveats: * * - If IP_FW_PORT_TEE_FLAG is set, tee the packet instead * of diverting it (ie, 'ipfw tee'). * * - If IP_FW_PORT_DYNT_FLAG is set, interpret the lower * 16 bits as a dummynet pipe number instead of diverting */ static int ip_fw_chk(struct ip **pip, int hlen, struct ifnet *oif, u_int16_t *cookie, struct mbuf **m, struct ip_fw_chain **flow_id, struct sockaddr_in **next_hop) { struct ip_fw_chain *chain; struct ip_fw *f = NULL, *rule = NULL; struct ip *ip = *pip; struct ifnet *const rif = (*m)->m_pkthdr.rcvif; struct ifnet *tif; u_short offset = 0 ; u_short src_port = 0, dst_port = 0; struct in_addr src_ip, dst_ip; /* XXX */ u_int8_t proto= 0, flags = 0 ; /* XXX */ u_int16_t skipto, bridgeCookie; u_int16_t ip_len; int dyn_checked = 0 ; /* set after dyn.rules have been checked. */ int direction = MATCH_FORWARD ; /* dirty trick... */ struct ipfw_dyn_rule *q = NULL ; /* Special hack for bridging (as usual) */ if (cookie == NULL) { bridgeCookie = 0; cookie = &bridgeCookie; #define BRIDGED (cookie == &bridgeCookie) hlen = ip->ip_hl << 2; } /* Grab and reset cookie */ skipto = *cookie; *cookie = 0; #define PULLUP_TO(len) do { \ if ((*m)->m_len < (len)) { \ ip = NULL ; \ if ((*m = m_pullup(*m, (len))) == 0) \ goto bogusfrag; \ ip = mtod(*m, struct ip *); \ *pip = ip; \ } \ } while (0) /* * Collect parameters into local variables for faster matching. */ proto = ip->ip_p; src_ip = ip->ip_src; dst_ip = ip->ip_dst; if (0 && BRIDGED) { /* not yet... */ offset = (ntohs(ip->ip_off) & IP_OFFMASK); ip_len = ntohs(ip->ip_len); } else { offset = (ip->ip_off & IP_OFFMASK); ip_len = ip->ip_len; } if (offset == 0) { - struct tcphdr *tcp; - struct udphdr *udp; - switch (proto) { - case IPPROTO_TCP : + case IPPROTO_TCP : { + struct tcphdr *tcp; + PULLUP_TO(hlen + sizeof(struct tcphdr)); tcp =(struct tcphdr *)((u_int32_t *)ip + ip->ip_hl); dst_port = tcp->th_dport ; src_port = tcp->th_sport ; flags = tcp->th_flags ; + } break ; - case IPPROTO_UDP : + case IPPROTO_UDP : { + struct udphdr *udp; + PULLUP_TO(hlen + sizeof(struct udphdr)); udp =(struct udphdr *)((u_int32_t *)ip + ip->ip_hl); dst_port = udp->uh_dport ; src_port = udp->uh_sport ; + } break; case IPPROTO_ICMP: PULLUP_TO(hlen + 4); /* type, code and checksum. */ flags = ((struct icmp *) ((u_int32_t *)ip + ip->ip_hl))->icmp_type ; break ; default : break; } } #undef PULLUP_TO last_pkt.src_ip = ntohl(src_ip.s_addr); last_pkt.dst_ip = ntohl(dst_ip.s_addr); last_pkt.proto = proto; last_pkt.src_port = ntohs(src_port); last_pkt.dst_port = ntohs(dst_port); last_pkt.flags = flags; if (*flow_id) { /* Accept if passed first test */ if (fw_one_pass) return 0; /* * Packet has already been tagged. Look for the next rule * to restart processing. */ chain = LIST_NEXT(*flow_id, next); if ((chain = (*flow_id)->rule->next_rule_ptr) == NULL) chain = (*flow_id)->rule->next_rule_ptr = lookup_next_rule(*flow_id); if (chain == NULL) goto dropit; } else { /* * Go down the chain, looking for enlightment. * If we've been asked to start at a given rule, do so. */ chain = LIST_FIRST(&ip_fw_chain_head); if (skipto != 0) { if (skipto >= IPFW_DEFAULT_RULE) goto dropit; while (chain && chain->rule->fw_number <= skipto) chain = LIST_NEXT(chain, next); if (chain == NULL) goto dropit; } } for (; chain; chain = LIST_NEXT(chain, next)) { again: f = chain->rule; if (f->fw_number == IPFW_DEFAULT_RULE) goto got_match ; /* * dynamic rules are checked at the first keep-state or * check-state occurrence. */ if (f->fw_flg & (IP_FW_F_KEEP_S|IP_FW_F_CHECK_S) && dyn_checked == 0 ) { dyn_checked = 1 ; q = lookup_dyn_rule(&last_pkt, &direction); if (q != NULL) { DEB(printf("-- dynamic match 0x%08x %d %s 0x%08x %d\n", (q->id.src_ip), (q->id.src_port), (direction == MATCH_FORWARD ? "-->" : "<--"), (q->id.dst_ip), (q->id.dst_port) ); ) chain = q->chain ; f = chain->rule ; q->pcnt++ ; q->bcnt += ip_len; goto got_match ; /* random not allowed here */ } /* if this was a check-only rule, continue with next */ if (f->fw_flg & IP_FW_F_CHECK_S) continue ; } /* Check if rule only valid for bridged packets */ if ((f->fw_flg & IP_FW_BRIDGED) != 0 && !(BRIDGED)) continue; if (oif) { /* Check direction outbound */ if (!(f->fw_flg & IP_FW_F_OUT)) continue; } else { /* Check direction inbound */ if (!(f->fw_flg & IP_FW_F_IN)) continue; } /* Fragments */ if ((f->fw_flg & IP_FW_F_FRAG) && offset == 0 ) continue; if (f->fw_flg & IP_FW_F_SME) { INADDR_TO_IFP(src_ip, tif); if (tif == NULL) continue; } if (f->fw_flg & IP_FW_F_DME) { INADDR_TO_IFP(dst_ip, tif); if (tif == NULL) continue; } /* If src-addr doesn't match, not this rule. */ if (((f->fw_flg & IP_FW_F_INVSRC) != 0) ^ ((src_ip.s_addr & f->fw_smsk.s_addr) != f->fw_src.s_addr)) continue; /* If dest-addr doesn't match, not this rule. */ if (((f->fw_flg & IP_FW_F_INVDST) != 0) ^ ((dst_ip.s_addr & f->fw_dmsk.s_addr) != f->fw_dst.s_addr)) continue; /* Interface check */ if ((f->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { struct ifnet *const iface = oif ? oif : rif; /* Backwards compatibility hack for "via" */ if (!iface || !iface_match(iface, &f->fw_in_if, f->fw_flg & IP_FW_F_OIFNAME)) continue; } else { /* Check receive interface */ if ((f->fw_flg & IP_FW_F_IIFACE) && (!rif || !iface_match(rif, &f->fw_in_if, f->fw_flg & IP_FW_F_IIFNAME))) continue; /* Check outgoing interface */ if ((f->fw_flg & IP_FW_F_OIFACE) && (!oif || !iface_match(oif, &f->fw_out_if, f->fw_flg & IP_FW_F_OIFNAME))) continue; } /* Check IP header values */ if (f->fw_ipflg & IP_FW_IF_IPOPT && !ipopts_match(ip, f)) continue; if (f->fw_ipflg & IP_FW_IF_IPLEN && f->fw_iplen != ip_len) continue; if (f->fw_ipflg & IP_FW_IF_IPID && f->fw_ipid != ntohs(ip->ip_id)) continue; if (f->fw_ipflg & IP_FW_IF_IPTOS && !iptos_match(ip, f)) continue; if (f->fw_ipflg & IP_FW_IF_IPTTL && f->fw_ipttl != ip->ip_ttl) continue; if (f->fw_ipflg & IP_FW_IF_IPVER && f->fw_ipver != ip->ip_v) continue; /* Check protocol; if wildcard, and no [ug]id, match */ if (f->fw_prot == IPPROTO_IP) { if (!(f->fw_flg & (IP_FW_F_UID|IP_FW_F_GID))) goto rnd_then_got_match; } else /* If different, don't match */ if (proto != f->fw_prot) continue; /* Protocol specific checks for uid only */ if (f->fw_flg & (IP_FW_F_UID|IP_FW_F_GID)) { switch (proto) { case IPPROTO_TCP: { struct inpcb *P; if (offset == 1) /* cf. RFC 1858 */ goto bogusfrag; if (offset != 0) continue; if (oif) P = in_pcblookup_hash(&tcbinfo, dst_ip, dst_port, src_ip, src_port, 0, oif); else P = in_pcblookup_hash(&tcbinfo, src_ip, src_port, dst_ip, dst_port, 0, NULL); if (P && P->inp_socket) { if (f->fw_flg & IP_FW_F_UID) { if (P->inp_socket->so_cred->cr_uid != f->fw_uid) continue; } else if (!groupmember(f->fw_gid, P->inp_socket->so_cred)) continue; } else continue; break; } case IPPROTO_UDP: { struct inpcb *P; if (offset != 0) continue; if (oif) P = in_pcblookup_hash(&udbinfo, dst_ip, dst_port, src_ip, src_port, 1, oif); else P = in_pcblookup_hash(&udbinfo, src_ip, src_port, dst_ip, dst_port, 1, NULL); if (P && P->inp_socket) { if (f->fw_flg & IP_FW_F_UID) { if (P->inp_socket->so_cred->cr_uid != f->fw_uid) continue; } else if (!groupmember(f->fw_gid, P->inp_socket->so_cred)) continue; } else continue; break; } default: continue; } } /* Protocol specific checks */ switch (proto) { case IPPROTO_TCP: { struct tcphdr *tcp; if (offset == 1) /* cf. RFC 1858 */ goto bogusfrag; if (offset != 0) { /* * TCP flags and ports aren't available in this * packet -- if this rule specified either one, * we consider the rule a non-match. */ if (f->fw_nports != 0 || f->fw_ipflg & IP_FW_IF_TCPMSK) continue; break; } tcp = (struct tcphdr *) ((u_int32_t *)ip + ip->ip_hl); if (f->fw_ipflg & IP_FW_IF_TCPOPT && !tcpopts_match(tcp, f)) continue; if (((f->fw_ipflg & IP_FW_IF_TCPFLG) || (f->fw_ipflg & IP_FW_IF_TCPEST)) && !tcpflg_match(tcp, f)) continue; if (f->fw_ipflg & IP_FW_IF_TCPSEQ && tcp->th_seq != f->fw_tcpseq) continue; if (f->fw_ipflg & IP_FW_IF_TCPACK && tcp->th_ack != f->fw_tcpack) continue; if (f->fw_ipflg & IP_FW_IF_TCPWIN && tcp->th_win != f->fw_tcpwin) continue; goto check_ports; } case IPPROTO_UDP: if (offset != 0) { /* * Port specification is unavailable -- if this * rule specifies a port, we consider the rule * a non-match. */ if (f->fw_nports != 0) continue; break; } check_ports: if (!port_match(&f->fw_uar.fw_pts[0], IP_FW_GETNSRCP(f), ntohs(src_port), f->fw_flg & IP_FW_F_SRNG, f->fw_flg & IP_FW_F_SMSK)) continue; if (!port_match(&f->fw_uar.fw_pts[IP_FW_GETNSRCP(f)], IP_FW_GETNDSTP(f), ntohs(dst_port), f->fw_flg & IP_FW_F_DRNG, f->fw_flg & IP_FW_F_DMSK)) continue; break; case IPPROTO_ICMP: { struct icmp *icmp; if (offset != 0) /* Type isn't valid */ break; icmp = (struct icmp *) ((u_int32_t *)ip + ip->ip_hl); if (!icmptype_match(icmp, f)) continue; break; } default: break; bogusfrag: if (fw_verbose && ip != NULL) ipfw_report(NULL, ip, offset, ip_len, rif, oif); goto dropit; } rnd_then_got_match: if ( ((struct ip_fw_ext *)f)->dont_match_prob && random() < ((struct ip_fw_ext *)f)->dont_match_prob ) continue ; got_match: /* * If not a dynamic match (q == NULL) and keep-state, install * a new dynamic entry. */ - if (q == NULL && f->fw_flg & IP_FW_F_KEEP_S) - install_state(chain); + if (q == NULL && f->fw_flg & IP_FW_F_KEEP_S) { + if (install_state(chain)) /* error or limit violation */ + goto dropit; + } /* Update statistics */ f->fw_pcnt += 1; f->fw_bcnt += ip_len; f->timestamp = time_second; /* Log to console if desired */ if ((f->fw_flg & IP_FW_F_PRN) && fw_verbose) ipfw_report(f, ip, offset, ip_len, rif, oif); /* Take appropriate action */ switch (f->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_ACCEPT: return(0); case IP_FW_F_COUNT: continue; #ifdef IPDIVERT case IP_FW_F_DIVERT: *cookie = f->fw_number; return(f->fw_divert_port); case IP_FW_F_TEE: *cookie = f->fw_number; return(f->fw_divert_port | IP_FW_PORT_TEE_FLAG); #endif case IP_FW_F_SKIPTO: /* XXX check */ - if ( f->next_rule_ptr ) - chain = f->next_rule_ptr ; - else + if ( (chain = f->next_rule_ptr) == NULL ) chain = lookup_next_rule(chain) ; - if (! chain) goto dropit; + if (! chain) + goto dropit; goto again ; #ifdef DUMMYNET case IP_FW_F_PIPE: case IP_FW_F_QUEUE: *flow_id = chain; return(f->fw_pipe_nr | IP_FW_PORT_DYNT_FLAG); #endif #ifdef IPFIREWALL_FORWARD case IP_FW_F_FWD: /* Change the next-hop address for this packet. * Initially we'll only worry about directly * reachable next-hop's, but ultimately * we will work out for next-hops that aren't * direct the route we would take for it. We * [cs]ould leave this latter problem to * ip_output.c. We hope to high [name the abode of * your favourite deity] that ip_output doesn't modify * the new value of next_hop (which is dst there) */ if (next_hop != NULL /* Make sure, first... */ && (q == NULL || direction == MATCH_FORWARD) ) *next_hop = &(f->fw_fwd_ip); return(0); /* Allow the packet */ #endif } /* Deny/reject this packet using this rule */ rule = f; break; } /* Rule IPFW_DEFAULT_RULE should always be there and match */ KASSERT(chain != NULL, ("ip_fw: no chain")); /* * At this point, we're going to drop the packet. * Send a reject notice if all of the following are true: * * - The packet matched a reject rule * - The packet is not an ICMP packet, or is an ICMP query packet * - The packet is not a multicast or broadcast packet */ if ((rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_REJECT - && (ip->ip_p != IPPROTO_ICMP || is_icmp_query(ip)) + && (proto != IPPROTO_ICMP || is_icmp_query(ip)) && !((*m)->m_flags & (M_BCAST|M_MCAST)) && !IN_MULTICAST(ntohl(ip->ip_dst.s_addr))) { switch (rule->fw_reject_code) { case IP_FW_REJECT_RST: { /* XXX warning, this code writes into the mbuf */ struct tcphdr *const tcp = (struct tcphdr *) ((u_int32_t *)ip + ip->ip_hl); struct tcpiphdr ti, *const tip = (struct tcpiphdr *) ip; if (offset != 0 || (tcp->th_flags & TH_RST)) break; ti.ti_i = *((struct ipovly *) ip); ti.ti_t = *tcp; bcopy(&ti, ip, sizeof(ti)); NTOHL(tip->ti_seq); NTOHL(tip->ti_ack); tip->ti_len = ip_len - hlen - (tip->ti_off << 2); if (tcp->th_flags & TH_ACK) { tcp_respond(NULL, (void *)ip, tcp, *m, (tcp_seq)0, tcp->th_ack, TH_RST); } else { if (tcp->th_flags & TH_SYN) tip->ti_len++; tcp_respond(NULL, (void *)ip, tcp, *m, tip->ti_seq + tip->ti_len, (tcp_seq)0, TH_RST|TH_ACK); } *m = NULL; break; } default: /* Send an ICMP unreachable using code */ icmp_error(*m, ICMP_UNREACH, rule->fw_reject_code, 0L, 0); *m = NULL; break; } } dropit: /* * Finally, drop the packet. */ return(IP_FW_PORT_DENY_FLAG); #undef BRIDGED } /* * when a rule is added/deleted, zero the direct pointers within * all firewall rules. These will be reconstructed on the fly * as packets are matched. * Must be called at splnet(). */ static void flush_rule_ptrs() { struct ip_fw_chain *fcp ; LIST_FOREACH(fcp, &ip_fw_chain_head, next) { fcp->rule->next_rule_ptr = NULL ; } } static int -add_entry(struct ip_fw_head *chainptr, struct ip_fw *frwl) +add_entry(struct ip_fw_head *head, struct ip_fw *rule) { struct ip_fw *ftmp = 0; struct ip_fw_ext *ftmp_ext = 0 ; - struct ip_fw_chain *fwc = 0, *fcp, *fcpl = 0; + struct ip_fw_chain *fwc, *fcp, *fcpl; u_short nbr = 0; int s; fwc = malloc(sizeof *fwc, M_IPFW, M_DONTWAIT); + if (!fwc) + return (ENOSPC); ftmp_ext = malloc(sizeof *ftmp_ext, M_IPFW, M_DONTWAIT | M_ZERO); - ftmp = &ftmp_ext->rule ; - if (!fwc || !ftmp) { - dprintf(("%s malloc said no\n", err_prefix)); - if (fwc) free(fwc, M_IPFW); - if (ftmp) free(ftmp, M_IPFW); + if (!ftmp_ext) { + free(fwc, M_IPFW); return (ENOSPC); } + fwc->rule = ftmp = &ftmp_ext->rule ; - bcopy(frwl, ftmp, sizeof(*ftmp)); + bcopy(rule, ftmp, sizeof(*ftmp)); if (ftmp->fw_flg & IP_FW_F_RND_MATCH) ftmp_ext->dont_match_prob = (intptr_t)ftmp->pipe_ptr; - if (ftmp->fw_flg & IP_FW_F_KEEP_S) - ftmp_ext->dyn_type = (u_long)(ftmp->next_rule_ptr) ; + if (ftmp->fw_flg & IP_FW_F_KEEP_S) { + u_long type = (u_long)(ftmp->next_rule_ptr) ; + ftmp_ext->dyn_type = type & 0xff; + } ftmp->fw_in_if.fu_via_if.name[FW_IFNLEN - 1] = '\0'; ftmp->fw_pcnt = 0L; ftmp->fw_bcnt = 0L; ftmp->next_rule_ptr = NULL ; ftmp->pipe_ptr = NULL ; - fwc->rule = ftmp; s = splnet(); - if (LIST_FIRST(chainptr) == 0) { - LIST_INSERT_HEAD(chainptr, fwc, next); - splx(s); - return(0); + if (LIST_FIRST(head) == 0) { + LIST_INSERT_HEAD(head, fwc, next); + goto done; } /* If entry number is 0, find highest numbered rule and add 100 */ if (ftmp->fw_number == 0) { - LIST_FOREACH(fcp, chainptr, next) { - if (fcp->rule->fw_number != (u_short)-1) + LIST_FOREACH(fcp, head, next) { + if (fcp->rule->fw_number != IPFW_DEFAULT_RULE) nbr = fcp->rule->fw_number; else break; } if (nbr < IPFW_DEFAULT_RULE - 100) nbr += 100; - ftmp->fw_number = frwl->fw_number = nbr; + ftmp->fw_number = rule->fw_number = nbr; } /* Got a valid number; now insert it, keeping the list ordered */ - LIST_FOREACH(fcp, chainptr, next) { + fcpl = NULL ; + LIST_FOREACH(fcp, head, next) { if (fcp->rule->fw_number > ftmp->fw_number) { if (fcpl) { LIST_INSERT_AFTER(fcpl, fwc, next); } else { - LIST_INSERT_HEAD(chainptr, fwc, next); + LIST_INSERT_HEAD(head, fwc, next); } break; } else { fcpl = fcp; } } flush_rule_ptrs(); - +done: + static_count++; splx(s); + DEB(printf("++ installed rule %d, static count now %d\n", + ftmp->fw_number, static_count);) return (0); } -static int -del_entry(struct ip_fw_head *chainptr, u_short number) +/** + * free storage associated with a static chain entry (including + * dependent dynamic rules), and zeroes rule pointers to avoid + * dangling pointer dereferences. + * @return a pointer to the next entry. + * Must be called at splnet() and with a non-null argument. + */ +static struct ip_fw_chain * +free_chain(struct ip_fw_chain *fcp) { - struct ip_fw_chain *fcp; + struct ip_fw_chain *n; - fcp = LIST_FIRST(chainptr); - if (number != (u_short)-1) { - for (; fcp; fcp = LIST_NEXT(fcp, next)) { - if (fcp->rule->fw_number == number) { - int s; - - /* prevent access to rules while removing them */ - s = splnet(); - while (fcp && fcp->rule->fw_number == number) { - struct ip_fw_chain *next; - - remove_dyn_rule(fcp, 1 /* delete */); - next = LIST_NEXT(fcp, next); - LIST_REMOVE(fcp, next); + n = LIST_NEXT(fcp, next); + DELETE_DYN_CHAIN(fcp); + LIST_REMOVE(fcp, next); + static_count--; #ifdef DUMMYNET - dn_rule_delete(fcp) ; + dn_rule_delete(fcp) ; #endif - flush_rule_ptrs(); - free(fcp->rule, M_IPFW); - free(fcp, M_IPFW); - fcp = next; - } - splx(s); - return 0; - } - } - } - - return (EINVAL); + flush_rule_ptrs(); /* more efficient to do outside the loop */ + free(fcp->rule, M_IPFW); + free(fcp, M_IPFW); + return n; } +/** + * remove all rules with given number. + */ static int -zero_entry(struct ip_fw *frwl) +del_entry(struct ip_fw_head *chainptr, u_short number) { - struct ip_fw_chain *fcp; - int s, cleared; + struct ip_fw_chain *fcp; - if (frwl == 0) { - s = splnet(); - LIST_FOREACH(fcp, &ip_fw_chain_head, next) { - fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; - fcp->rule->fw_loghighest = fcp->rule->fw_logamount; - fcp->rule->timestamp = 0; - } + if (number != IPFW_DEFAULT_RULE) { + LIST_FOREACH(fcp, chainptr, next) { + if (fcp->rule->fw_number == number) { + int s ; + + s = splnet(); /* prevent access to rules while removing */ + while (fcp && fcp->rule->fw_number == number) + fcp = free_chain(fcp); + /* XXX could move flush_rule_ptrs() here */ splx(s); + return 0 ; + } } - else { - cleared = 0; - - /* - * It's possible to insert multiple chain entries with the - * same number, so we don't stop after finding the first - * match if zeroing a specific entry. - */ - LIST_FOREACH(fcp, &ip_fw_chain_head, next) - if (frwl->fw_number == fcp->rule->fw_number) { - s = splnet(); - while (fcp && frwl->fw_number == fcp->rule->fw_number) { - fcp->rule->fw_bcnt = fcp->rule->fw_pcnt = 0; - fcp->rule->fw_loghighest = - fcp->rule->fw_logamount; - fcp->rule->timestamp = 0; - fcp = LIST_NEXT(fcp, next); - } - splx(s); - cleared = 1; - break; - } - if (!cleared) /* we didn't find any matching rules */ - return (EINVAL); - } - - if (fw_verbose) { - if (frwl) - log(LOG_SECURITY | LOG_NOTICE, - "ipfw: Entry %d cleared.\n", frwl->fw_number); - else - log(LOG_SECURITY | LOG_NOTICE, - "ipfw: Accounting cleared.\n"); - } - - return (0); + } + return (EINVAL); } +/** + * Reset some or all counters on firewall rules. + * @arg frwl is null to clear all entries, or contains a specific + * rule number. + * @arg log_only is 1 if we only want to reset logs, zero otherwise. + */ + static int -resetlog_entry(struct ip_fw *frwl) +zero_entry(struct ip_fw *frwl, int log_only) { - struct ip_fw_chain *fcp; - int s, cleared; + struct ip_fw_chain *fcp; + struct ip_fw *rule; + int s; + u_short number = 0 ; + char *msg ; - if (frwl == 0) { - s = splnet(); - counter = 0; - LIST_FOREACH(fcp, &ip_fw_chain_head, next) - fcp->rule->fw_loghighest = fcp->rule->fw_pcnt + - fcp->rule->fw_logamount; - splx(s); + if (frwl == 0) { + s = splnet(); + LIST_FOREACH(fcp, &ip_fw_chain_head, next) { + rule = fcp->rule; + if (log_only == 0) { + rule->fw_bcnt = rule->fw_pcnt = 0; + rule->timestamp = 0; + } + rule->fw_loghighest = rule->fw_pcnt+rule->fw_logamount; } - else { - cleared = 0; + splx(s); + msg = log_only ? "ipfw: All logging counts cleared.\n" : + "ipfw: Accounting cleared.\n"; + } else { + int cleared = 0; + number = frwl->fw_number ; - /* - * It's possible to insert multiple chain entries with the - * same number, so we don't stop after finding the first - * match if zeroing a specific entry. - */ - LIST_FOREACH(fcp, &ip_fw_chain_head, next) - if (frwl->fw_number == fcp->rule->fw_number) { - s = splnet(); - while (fcp && frwl->fw_number == fcp->rule->fw_number) { - fcp->rule->fw_loghighest = - fcp->rule->fw_pcnt + - fcp->rule->fw_logamount; - fcp = LIST_NEXT(fcp, next); - } - splx(s); - cleared = 1; - break; - } - if (!cleared) /* we didn't find any matching rules */ - return (EINVAL); - } - - if (fw_verbose) { - if (frwl) - log(LOG_SECURITY | LOG_NOTICE, - "ipfw: Entry %d logging count reset.\n", - frwl->fw_number); - else - log(LOG_SECURITY | LOG_NOTICE, " - ipfw: All logging counts cleared.\n"); - } - - return (0); + /* + * It's possible to insert multiple chain entries with the + * same number, so we don't stop after finding the first + * match if zeroing a specific entry. + */ + LIST_FOREACH(fcp, &ip_fw_chain_head, next) + if (number == fcp->rule->fw_number) { + s = splnet(); + while (fcp && number == (rule=fcp->rule)->fw_number) { + if (log_only == 0) { + rule->fw_bcnt = rule->fw_pcnt = 0; + rule->timestamp = 0; + } + rule->fw_loghighest = rule->fw_pcnt+ rule->fw_logamount; + fcp = LIST_NEXT(fcp, next); + } + splx(s); + cleared = 1; + break; + } + if (!cleared) /* we didn't find any matching rules */ + return (EINVAL); + msg = log_only ? "Entry %d logging count reset.\n" : + "ipfw: Entry %d cleared.\n"; + } + if (fw_verbose) + log(LOG_SECURITY | LOG_NOTICE, msg, number); + return (0); } static int check_ipfw_struct(struct ip_fw *frwl) { /* Check for invalid flag bits */ if ((frwl->fw_flg & ~IP_FW_F_MASK) != 0) { dprintf(("%s undefined flag bits set (flags=%x)\n", err_prefix, frwl->fw_flg)); return (EINVAL); } if (frwl->fw_flg == IP_FW_F_CHECK_S) { /* check-state */ return 0 ; } /* Must apply to incoming or outgoing (or both) */ if (!(frwl->fw_flg & (IP_FW_F_IN | IP_FW_F_OUT))) { dprintf(("%s neither in nor out\n", err_prefix)); return (EINVAL); } /* Empty interface name is no good */ if (((frwl->fw_flg & IP_FW_F_IIFNAME) && !*frwl->fw_in_if.fu_via_if.name) || ((frwl->fw_flg & IP_FW_F_OIFNAME) && !*frwl->fw_out_if.fu_via_if.name)) { dprintf(("%s empty interface name\n", err_prefix)); return (EINVAL); } /* Sanity check interface matching */ if ((frwl->fw_flg & IF_FW_F_VIAHACK) == IF_FW_F_VIAHACK) { ; /* allow "via" backwards compatibility */ } else if ((frwl->fw_flg & IP_FW_F_IN) && (frwl->fw_flg & IP_FW_F_OIFACE)) { dprintf(("%s outgoing interface check on incoming\n", err_prefix)); return (EINVAL); } /* Sanity check port ranges */ if ((frwl->fw_flg & IP_FW_F_SRNG) && IP_FW_GETNSRCP(frwl) < 2) { dprintf(("%s src range set but n_src_p=%d\n", err_prefix, IP_FW_GETNSRCP(frwl))); return (EINVAL); } if ((frwl->fw_flg & IP_FW_F_DRNG) && IP_FW_GETNDSTP(frwl) < 2) { dprintf(("%s dst range set but n_dst_p=%d\n", err_prefix, IP_FW_GETNDSTP(frwl))); return (EINVAL); } if (IP_FW_GETNSRCP(frwl) + IP_FW_GETNDSTP(frwl) > IP_FW_MAX_PORTS) { dprintf(("%s too many ports (%d+%d)\n", err_prefix, IP_FW_GETNSRCP(frwl), IP_FW_GETNDSTP(frwl))); return (EINVAL); } /* * Protocols other than TCP/UDP don't use port range */ if ((frwl->fw_prot != IPPROTO_TCP) && (frwl->fw_prot != IPPROTO_UDP) && (IP_FW_GETNSRCP(frwl) || IP_FW_GETNDSTP(frwl))) { dprintf(("%s port(s) specified for non TCP/UDP rule\n", err_prefix)); return (EINVAL); } /* * Rather than modify the entry to make such entries work, * we reject this rule and require user level utilities * to enforce whatever policy they deem appropriate. */ if ((frwl->fw_src.s_addr & (~frwl->fw_smsk.s_addr)) || (frwl->fw_dst.s_addr & (~frwl->fw_dmsk.s_addr))) { dprintf(("%s rule never matches\n", err_prefix)); return (EINVAL); } if ((frwl->fw_flg & IP_FW_F_FRAG) && (frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) { if (frwl->fw_nports) { dprintf(("%s cannot mix 'frag' and ports\n", err_prefix)); return (EINVAL); } if (frwl->fw_prot == IPPROTO_TCP && frwl->fw_tcpf != frwl->fw_tcpnf) { dprintf(("%s cannot mix 'frag' and TCP flags\n", err_prefix)); return (EINVAL); } } if (frwl->fw_flg & (IP_FW_F_UID | IP_FW_F_GID)) { if ((frwl->fw_prot != IPPROTO_TCP) && (frwl->fw_prot != IPPROTO_UDP) && (frwl->fw_prot != IPPROTO_IP)) { dprintf(("%s cannot use uid/gid logic on non-TCP/UDP\n", err_prefix)); return (EINVAL); } } /* Check command specific stuff */ - switch (frwl->fw_flg & IP_FW_F_COMMAND) - { + switch (frwl->fw_flg & IP_FW_F_COMMAND) { case IP_FW_F_REJECT: if (frwl->fw_reject_code >= 0x100 && !(frwl->fw_prot == IPPROTO_TCP && frwl->fw_reject_code == IP_FW_REJECT_RST)) { dprintf(("%s unknown reject code\n", err_prefix)); return (EINVAL); } break; #if defined(IPDIVERT) || defined(DUMMYNET) #ifdef IPDIVERT case IP_FW_F_DIVERT: /* Diverting to port zero is invalid */ case IP_FW_F_TEE: #endif #ifdef DUMMYNET - case IP_FW_F_PIPE: /* piping through 0 is invalid */ - case IP_FW_F_QUEUE: /* piping through 0 is invalid */ + case IP_FW_F_PIPE: /* pipe 0 is invalid */ + case IP_FW_F_QUEUE: /* queue 0 is invalid */ #endif if (frwl->fw_divert_port == 0) { - dprintf(("%s can't divert to port 0\n", err_prefix)); + dprintf(("%s 0 is an invalid argument\n", err_prefix)); return (EINVAL); } break; #endif /* IPDIVERT || DUMMYNET */ case IP_FW_F_DENY: case IP_FW_F_ACCEPT: case IP_FW_F_COUNT: case IP_FW_F_SKIPTO: #ifdef IPFIREWALL_FORWARD case IP_FW_F_FWD: #endif break; default: dprintf(("%s invalid command\n", err_prefix)); return (EINVAL); } return 0; } static int ip_fw_ctl(struct sockopt *sopt) { int error, s; size_t size; struct ip_fw_chain *fcp; struct ip_fw frwl, *bp , *buf; /* * Disallow modifications in really-really secure mode, but still allow * the logging counters to be reset. */ if (securelevel >= 3 && (sopt->sopt_name == IP_FW_ADD || (sopt->sopt_dir == SOPT_SET && sopt->sopt_name != IP_FW_RESETLOG))) return (EPERM); error = 0; switch (sopt->sopt_name) { case IP_FW_GET: - size = 0 ; s = splnet(); - LIST_FOREACH(fcp, &ip_fw_chain_head, next) - size += sizeof(struct ip_fw) ; - if (ipfw_dyn_v) { - int i ; - struct ipfw_dyn_rule *p ; + /* size of static rules */ + size = static_count * sizeof(struct ip_fw) ; + if (ipfw_dyn_v) /* add size of dyn.rules */ + size += (dyn_count * sizeof(struct ipfw_dyn_rule)); - for (i = 0 ; i < curr_dyn_buckets ; i++ ) - for ( p = ipfw_dyn_v[i] ; p != NULL ; p = p->next ) - size += sizeof(*p) ; - } + /* + * XXX todo: if the user passes a short length to know how + * much room is needed, do not + * bother filling up the buffer, just jump to the + * sooptcopyout. + */ buf = malloc(size, M_TEMP, M_WAITOK); if (buf == 0) { + splx(s); error = ENOBUFS; break; } bp = buf ; LIST_FOREACH(fcp, &ip_fw_chain_head, next) { - bcopy(fcp->rule, bp, sizeof *fcp->rule); - bp->pipe_ptr = (void *)(intptr_t) - ((struct ip_fw_ext *)fcp->rule)->dont_match_prob; - bp->next_rule_ptr = (void *)(intptr_t) - ((struct ip_fw_ext *)fcp->rule)->dyn_type; - bp++; + struct ip_fw_ext *e = (struct ip_fw_ext *)fcp->rule; + + bcopy(e, bp, sizeof *fcp->rule); + bp->pipe_ptr = (void *)(intptr_t) e->dont_match_prob; + bp->next_rule_ptr = (void *)(intptr_t) (e->dyn_type); + bp++; } if (ipfw_dyn_v) { int i ; struct ipfw_dyn_rule *p, *dst, *last = NULL ; dst = (struct ipfw_dyn_rule *)bp ; for (i = 0 ; i < curr_dyn_buckets ; i++ ) for ( p = ipfw_dyn_v[i] ; p != NULL ; p = p->next, dst++ ) { bcopy(p, dst, sizeof *p); (int)dst->chain = p->chain->rule->fw_number ; dst->next = dst ; /* fake non-null pointer... */ last = dst ; if (TIME_LEQ(dst->expire, time_second) ) dst->expire = 0 ; else dst->expire -= time_second ; } if (last != NULL) last->next = NULL ; } splx(s); error = sooptcopyout(sopt, buf, size); FREE(buf, M_TEMP); break; case IP_FW_FLUSH: + /* + * Normally we cannot release the lock on each iteration. + * We could do it here only because we start from the head all + * the times so there is no risk of missing some entries. + * On the other hand, the risk is that we end up with + * a very inconsistent ruleset, so better keep the lock + * around the whole cycle. + * + * XXX this code can be improved by resetting the head of + * the list to point to the default rule, and then freeing + * the old list without the need for a lock. + */ + s = splnet(); - remove_dyn_rule(NULL, 1 /* force delete */); + while ( (fcp = LIST_FIRST(&ip_fw_chain_head)) && + fcp->rule->fw_number != IPFW_DEFAULT_RULE ) + free_chain(fcp); splx(s); - fcp = LIST_FIRST(&ip_fw_chain_head); - while (fcp) { - struct ip_fw_chain *next; - next = LIST_NEXT(fcp, next); - if (fcp->rule->fw_number > fw_permanent_rules && - fcp->rule->fw_number != IPFW_DEFAULT_RULE ) { - s = splnet(); - LIST_REMOVE(fcp, next); -#ifdef DUMMYNET - dn_rule_delete(fcp); -#endif - FREE(fcp->rule, M_IPFW); - FREE(fcp, M_IPFW); - splx(s); - } - fcp = next; - } break; - case IP_FW_ZERO: - if (sopt->sopt_val != 0) { - error = sooptcopyin(sopt, &frwl, sizeof frwl, - sizeof frwl); - if (error || (error = zero_entry(&frwl))) - break; - } else { - error = zero_entry(0); - } - break; - case IP_FW_ADD: error = sooptcopyin(sopt, &frwl, sizeof frwl, sizeof frwl); if (error || (error = check_ipfw_struct(&frwl))) break; if (frwl.fw_number == IPFW_DEFAULT_RULE) { dprintf(("%s can't add rule %u\n", err_prefix, (unsigned)IPFW_DEFAULT_RULE)); error = EINVAL; } else { error = add_entry(&ip_fw_chain_head, &frwl); if (!error && sopt->sopt_dir == SOPT_GET) error = sooptcopyout(sopt, &frwl, sizeof frwl); } break; case IP_FW_DEL: error = sooptcopyin(sopt, &frwl, sizeof frwl, sizeof frwl); if (error) break; if (frwl.fw_number == IPFW_DEFAULT_RULE) { dprintf(("%s can't delete rule %u\n", err_prefix, (unsigned)IPFW_DEFAULT_RULE)); error = EINVAL; } else { error = del_entry(&ip_fw_chain_head, frwl.fw_number); } break; + case IP_FW_ZERO: case IP_FW_RESETLOG: + { + int cmd = (sopt->sopt_name == IP_FW_RESETLOG ); + void *arg = NULL ; + if (sopt->sopt_val != 0) { - error = sooptcopyin(sopt, &frwl, sizeof frwl, - sizeof frwl); - if (error || (error = resetlog_entry(&frwl))) - break; - } else { - error = resetlog_entry(0); + error = sooptcopyin(sopt, &frwl, sizeof frwl, sizeof frwl); + if (error) + break ; + arg = &frwl ; } - break; + error = zero_entry(arg, cmd); + } + break; default: printf("ip_fw_ctl invalid option %d\n", sopt->sopt_name); error = EINVAL ; } return (error); } +/** + * dummynet needs a reference to the default rule, because rules can + * be deleted while packets hold a reference to them (e.g. to resume + * processing at the next rule). When this happens, dummynet changes + * the reference to the default rule (probably it could well be a + * NULL pointer, but this way we do not need to check for the special + * case, plus here he have info on the default behaviour. + */ struct ip_fw_chain *ip_fw_default_rule ; void ip_fw_init(void) { struct ip_fw default_rule; ip_fw_chk_ptr = ip_fw_chk; ip_fw_ctl_ptr = ip_fw_ctl; LIST_INIT(&ip_fw_chain_head); bzero(&default_rule, sizeof default_rule); default_rule.fw_prot = IPPROTO_IP; default_rule.fw_number = IPFW_DEFAULT_RULE; #ifdef IPFIREWALL_DEFAULT_TO_ACCEPT default_rule.fw_flg |= IP_FW_F_ACCEPT; #else default_rule.fw_flg |= IP_FW_F_DENY; #endif default_rule.fw_flg |= IP_FW_F_IN | IP_FW_F_OUT; if (check_ipfw_struct(&default_rule) != 0 || add_entry(&ip_fw_chain_head, &default_rule)) panic("ip_fw_init"); ip_fw_default_rule = LIST_FIRST(&ip_fw_chain_head) ; printf("IP packet filtering initialized, " #ifdef IPDIVERT "divert enabled, " #else "divert disabled, " #endif #ifdef IPFIREWALL_FORWARD "rule-based forwarding enabled, " #else "rule-based forwarding disabled, " #endif #ifdef IPFIREWALL_DEFAULT_TO_ACCEPT "default to accept, "); #else "default to deny, " ); #endif #ifndef IPFIREWALL_VERBOSE printf("logging disabled\n"); #else if (fw_verbose_limit == 0) printf("unlimited logging\n"); else printf("logging limited to %d packets/entry by default\n", fw_verbose_limit); #endif } static ip_fw_chk_t *old_chk_ptr; static ip_fw_ctl_t *old_ctl_ptr; static int ipfw_modevent(module_t mod, int type, void *unused) { int s; struct ip_fw_chain *fcp; switch (type) { case MOD_LOAD: s = splnet(); old_chk_ptr = ip_fw_chk_ptr; old_ctl_ptr = ip_fw_ctl_ptr; ip_fw_init(); splx(s); return 0; case MOD_UNLOAD: s = splnet(); ip_fw_chk_ptr = old_chk_ptr; ip_fw_ctl_ptr = old_ctl_ptr; - remove_dyn_rule(NULL, 1 /* force delete */); - while ( (fcp = LIST_FIRST(&ip_fw_chain_head)) != NULL) { - LIST_REMOVE(fcp, next); -#ifdef DUMMYNET - dn_rule_delete(fcp); -#endif - free(fcp->rule, M_IPFW); - free(fcp, M_IPFW); - } - + while ( (fcp = LIST_FIRST(&ip_fw_chain_head)) != NULL) + free_chain(fcp); splx(s); printf("IP firewall unloaded\n"); return 0; default: break; } return 0; } static moduledata_t ipfwmod = { "ipfw", ipfw_modevent, 0 }; DECLARE_MODULE(ipfw, ipfwmod, SI_SUB_PSEUDO, SI_ORDER_ANY); Index: head/sys/netinet/ip_fw.h =================================================================== --- head/sys/netinet/ip_fw.h (revision 83724) +++ head/sys/netinet/ip_fw.h (revision 83725) @@ -1,309 +1,324 @@ /* * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * * 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. * * $FreeBSD$ */ #ifndef _IP_FW_H #define _IP_FW_H #include /* * This union structure identifies an interface, either explicitly * by name or implicitly by IP address. The flags IP_FW_F_IIFNAME * and IP_FW_F_OIFNAME say how to interpret this structure. An * interface unit number of -1 matches any unit number, while an * IP address of 0.0.0.0 indicates matches any interface. * * The receive and transmit interfaces are only compared against the * the packet if the corresponding bit (IP_FW_F_IIFACE or IP_FW_F_OIFACE) * is set. Note some packets lack a receive or transmit interface * (in which case the missing "interface" never matches). */ union ip_fw_if { struct in_addr fu_via_ip; /* Specified by IP address */ struct { /* Specified by interface name */ #define FW_IFNLEN 10 /* need room ! was IFNAMSIZ */ char name[FW_IFNLEN]; short unit; /* -1 means match any unit */ } fu_via_if; }; /* * Format of an IP firewall descriptor * * fw_src, fw_dst, fw_smsk, fw_dmsk are always stored in network byte order. * fw_flg and fw_n*p are stored in host byte order (of course). * Port numbers are stored in HOST byte order. */ struct ip_fw { u_int64_t fw_pcnt,fw_bcnt; /* Packet and byte counters */ struct in_addr fw_src, fw_dst; /* Source and destination IP addr */ struct in_addr fw_smsk, fw_dmsk; /* Mask for src and dest IP addr */ u_short fw_number; /* Rule number */ u_int fw_flg; /* Operational Flags word */ #define IP_FW_MAX_PORTS 10 /* A reasonable maximum */ union { u_short fw_pts[IP_FW_MAX_PORTS]; /* Array of port numbers to match */ #define IP_FW_ICMPTYPES_MAX 128 #define IP_FW_ICMPTYPES_DIM (IP_FW_ICMPTYPES_MAX / (sizeof(unsigned) * 8)) unsigned fw_icmptypes[IP_FW_ICMPTYPES_DIM]; /* ICMP types bitmap */ } fw_uar; u_int fw_ipflg; /* IP flags word */ u_char fw_ipopt,fw_ipnopt; /* IP options set/unset */ u_short fw_iplen, fw_ipid; /* IP length, identification */ u_char fw_iptos, fw_ipntos; /* IP type of service set/unset */ u_char fw_ipttl; /* IP time to live */ u_int fw_ipver:4; /* IP version */ u_char fw_tcpopt,fw_tcpnopt; /* TCP options set/unset */ u_char fw_tcpf,fw_tcpnf; /* TCP flags set/unset */ u_int32_t fw_tcpseq, fw_tcpack; /* TCP sequence and acknowledgement */ u_short fw_tcpwin; /* TCP window size */ long timestamp; /* timestamp (tv_sec) of last match */ union ip_fw_if fw_in_if, fw_out_if; /* Incoming and outgoing interfaces */ union { u_short fu_divert_port; /* Divert/tee port (options IPDIVERT) */ u_short fu_pipe_nr; /* queue number (option DUMMYNET) */ u_short fu_skipto_rule; /* SKIPTO command rule number */ u_short fu_reject_code; /* REJECT response code */ struct sockaddr_in fu_fwd_ip; } fw_un; u_char fw_prot; /* IP protocol */ /* * N'of src ports and # of dst ports in ports array (dst ports * follow src ports; max of 10 ports in all; count of 0 means * match all ports) */ u_char fw_nports; void *pipe_ptr; /* flow_set ptr for dummynet pipe */ void *next_rule_ptr ; /* next rule in case of match */ uid_t fw_uid; /* uid to match */ gid_t fw_gid; /* gid to match */ int fw_logamount; /* amount to log */ u_int64_t fw_loghighest; /* highest number packet to log */ }; /* * extended ipfw structure... some fields in the original struct * can be used to pass parameters up/down, namely pointers * void *pipe_ptr * void *next_rule_ptr * some others can be used to pass parameters down, namely counters etc. * u_int64_t fw_pcnt,fw_bcnt; * long timestamp; */ struct ip_fw_ext { /* extended structure */ struct ip_fw rule; /* must be at offset 0 */ - long dont_match_prob; /* 0x7fffffff means 1.0, always fail */ - u_int dyn_type; /* type for dynamic rule */ + long dont_match_prob; /* 0x7fffffff means 1.0, always fail */ + u_char dyn_type; /* type for dynamic rule */ +#define DYN_KEEP_STATE 0 /* type for keep-state rules */ + u_char _pad1 ; /* for future use */ + u_short _pad2 ; /* for future use */ }; #define IP_FW_GETNSRCP(rule) ((rule)->fw_nports & 0x0f) #define IP_FW_SETNSRCP(rule, n) do { \ (rule)->fw_nports &= ~0x0f; \ (rule)->fw_nports |= (n); \ } while (0) #define IP_FW_GETNDSTP(rule) ((rule)->fw_nports >> 4) #define IP_FW_SETNDSTP(rule, n) do { \ (rule)->fw_nports &= ~0xf0; \ (rule)->fw_nports |= (n) << 4;\ } while (0) #define fw_divert_port fw_un.fu_divert_port #define fw_skipto_rule fw_un.fu_skipto_rule #define fw_reject_code fw_un.fu_reject_code #define fw_pipe_nr fw_un.fu_pipe_nr #define fw_fwd_ip fw_un.fu_fwd_ip +/** + * + * chain_ptr -------------+ + * V + * [ next.le_next ]---->[ next.le_next ]---- [ next.le_next ]---> + * [ next.le_prev ]<----[ next.le_prev ]<----[ next.le_prev ]<--- + * +--[ rule ] +--[ rule ] +--[ rule ] + * | | | + * +->[ ] +->[ ] +->[ ] + * + */ + struct ip_fw_chain { LIST_ENTRY(ip_fw_chain) next; struct ip_fw *rule; }; /* * Flow mask/flow id for each queue. */ struct ipfw_flow_id { u_int32_t dst_ip, src_ip ; u_int16_t dst_port, src_port ; u_int8_t proto ; u_int8_t flags ; /* protocol-specific flags */ } ; /* * dynamic ipfw rule */ struct ipfw_dyn_rule { struct ipfw_dyn_rule *next ; - struct ipfw_flow_id id ; - struct ipfw_flow_id mask ; - struct ip_fw_chain *chain ; /* pointer to parent rule */ - u_int32_t type ; /* rule type */ + struct ipfw_flow_id id ; /* (masked) flow id */ + struct ip_fw_chain *chain ; /* pointer to chain */ u_int32_t expire ; /* expire time */ u_int64_t pcnt, bcnt; /* match counters */ u_int32_t bucket ; /* which bucket in hash table */ u_int32_t state ; /* state of this rule (typ. a */ /* combination of TCP flags) */ + u_int16_t dyn_type; /* rule type */ + u_int16_t count; /* refcount */ } ; /* * Values for "flags" field . */ #define IP_FW_F_COMMAND 0x000000ff /* Mask for type of chain entry: */ #define IP_FW_F_DENY 0x00000000 /* This is a deny rule */ #define IP_FW_F_REJECT 0x00000001 /* Deny and send a response packet */ #define IP_FW_F_ACCEPT 0x00000002 /* This is an accept rule */ #define IP_FW_F_COUNT 0x00000003 /* This is a count rule */ #define IP_FW_F_DIVERT 0x00000004 /* This is a divert rule */ #define IP_FW_F_TEE 0x00000005 /* This is a tee rule */ #define IP_FW_F_SKIPTO 0x00000006 /* This is a skipto rule */ #define IP_FW_F_FWD 0x00000007 /* This is a "change forwarding address" rule */ #define IP_FW_F_PIPE 0x00000008 /* This is a dummynet rule */ #define IP_FW_F_QUEUE 0x00000009 /* This is a dummynet queue */ #define IP_FW_F_IN 0x00000100 /* Check inbound packets */ #define IP_FW_F_OUT 0x00000200 /* Check outbound packets */ #define IP_FW_F_IIFACE 0x00000400 /* Apply inbound interface test */ #define IP_FW_F_OIFACE 0x00000800 /* Apply outbound interface test */ #define IP_FW_F_PRN 0x00001000 /* Print if this rule matches */ #define IP_FW_F_SRNG 0x00002000 /* The first two src ports are a min * * and max range (stored in host byte * * order). */ #define IP_FW_F_DRNG 0x00004000 /* The first two dst ports are a min * * and max range (stored in host byte * * order). */ #define IP_FW_F_FRAG 0x00008000 /* Fragment */ #define IP_FW_F_IIFNAME 0x00010000 /* In interface by name/unit (not IP) */ #define IP_FW_F_OIFNAME 0x00020000 /* Out interface by name/unit (not IP) */ #define IP_FW_F_INVSRC 0x00040000 /* Invert sense of src check */ #define IP_FW_F_INVDST 0x00080000 /* Invert sense of dst check */ #define IP_FW_F_ICMPBIT 0x00100000 /* ICMP type bitmap is valid */ #define IP_FW_F_UID 0x00200000 /* filter by uid */ #define IP_FW_F_GID 0x00400000 /* filter by gid */ #define IP_FW_F_RND_MATCH 0x00800000 /* probabilistic rule match */ #define IP_FW_F_SMSK 0x01000000 /* src-port + mask */ #define IP_FW_F_DMSK 0x02000000 /* dst-port + mask */ #define IP_FW_BRIDGED 0x04000000 /* only match bridged packets */ #define IP_FW_F_KEEP_S 0x08000000 /* keep state */ #define IP_FW_F_CHECK_S 0x10000000 /* check state */ #define IP_FW_F_SME 0x20000000 /* source = me */ #define IP_FW_F_DME 0x40000000 /* destination = me */ #define IP_FW_F_MASK 0x7FFFFFFF /* All possible flag bits mask */ /* * Flags for the 'fw_ipflg' field, for comparing values of ip and its protocols. */ #define IP_FW_IF_TCPOPT 0x00000001 /* tcp options */ #define IP_FW_IF_TCPFLG 0x00000002 /* tcp flags */ #define IP_FW_IF_TCPSEQ 0x00000004 /* tcp sequence number */ #define IP_FW_IF_TCPACK 0x00000008 /* tcp acknowledgement number */ #define IP_FW_IF_TCPWIN 0x00000010 /* tcp window size */ #define IP_FW_IF_TCPEST 0x00000020 /* established TCP connection */ #define IP_FW_IF_TCPMSK 0x0000003f /* mask of all tcp values */ #define IP_FW_IF_IPOPT 0x00000100 /* ip options */ #define IP_FW_IF_IPLEN 0x00000200 /* ip length */ #define IP_FW_IF_IPID 0x00000400 /* ip identification */ #define IP_FW_IF_IPTOS 0x00000800 /* ip type of service */ #define IP_FW_IF_IPTTL 0x00001000 /* ip time to live */ #define IP_FW_IF_IPVER 0x00002000 /* ip version */ #define IP_FW_IF_IPMSK 0x00003f00 /* mask of all ip values */ #define IP_FW_IF_MSK 0x0000ffff /* All possible bits mask */ /* * For backwards compatibility with rules specifying "via iface" but * not restricted to only "in" or "out" packets, we define this combination * of bits to represent this configuration. */ #define IF_FW_F_VIAHACK (IP_FW_F_IN|IP_FW_F_OUT|IP_FW_F_IIFACE|IP_FW_F_OIFACE) /* * Definitions for REJECT response codes. * Values less than 256 correspond to ICMP unreachable codes. */ #define IP_FW_REJECT_RST 0x0100 /* TCP packets: send RST */ /* * Definitions for IP option names. */ #define IP_FW_IPOPT_LSRR 0x01 #define IP_FW_IPOPT_SSRR 0x02 #define IP_FW_IPOPT_RR 0x04 #define IP_FW_IPOPT_TS 0x08 /* * Definitions for TCP option names. */ #define IP_FW_TCPOPT_MSS 0x01 #define IP_FW_TCPOPT_WINDOW 0x02 #define IP_FW_TCPOPT_SACK 0x04 #define IP_FW_TCPOPT_TS 0x08 #define IP_FW_TCPOPT_CC 0x10 /* * Definitions for TCP flags. */ #define IP_FW_TCPF_FIN TH_FIN #define IP_FW_TCPF_SYN TH_SYN #define IP_FW_TCPF_RST TH_RST #define IP_FW_TCPF_PSH TH_PUSH #define IP_FW_TCPF_ACK TH_ACK #define IP_FW_TCPF_URG TH_URG /* * Main firewall chains definitions and global var's definitions. */ #ifdef _KERNEL #define IP_FW_PORT_DYNT_FLAG 0x10000 #define IP_FW_PORT_TEE_FLAG 0x20000 #define IP_FW_PORT_DENY_FLAG 0x40000 /* * Function definitions. */ void ip_fw_init __P((void)); /* Firewall hooks */ struct ip; struct sockopt; typedef int ip_fw_chk_t __P((struct ip **, int, struct ifnet *, u_int16_t *, struct mbuf **, struct ip_fw_chain **, struct sockaddr_in **)); typedef int ip_fw_ctl_t __P((struct sockopt *)); extern ip_fw_chk_t *ip_fw_chk_ptr; extern ip_fw_ctl_t *ip_fw_ctl_ptr; extern int fw_one_pass; extern int fw_enable; extern struct ipfw_flow_id last_pkt ; #endif /* _KERNEL */ #endif /* _IP_FW_H */