From 0fb106cc3f40524759012ac12baf28dccec9e571 Mon Sep 17 00:00:00 2001 From: luigi Date: Thu, 27 Sep 2001 23:44:27 +0000 Subject: Two main changes here: + implement "limit" rules, which permit to limit the number of sessions between certain host pairs (according to masks). These are a special type of stateful rules, which might be of interest in some cases. See the ipfw manpage for details. + merge the list pointers and ipfw rule descriptors in the kernel, so the code is smaller, faster and more readable. This patch basically consists in replacing "foo->rule->bar" with "rule->bar" all over the place. I have been willing to do this for ages! MFC after: 1 week --- sbin/ipfw/ipfw.8 | 58 ++++++++++++++++++++++++++++------------ sbin/ipfw/ipfw.c | 80 ++++++++++++++++++++++++++++++++++++++++++-------------- 2 files changed, 102 insertions(+), 36 deletions(-) (limited to 'sbin') diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 16f2873..475a746 100644 --- a/sbin/ipfw/ipfw.8 +++ b/sbin/ipfw/ipfw.8 @@ -69,14 +69,20 @@ traffic shaper in Each incoming or outgoing packet is passed through the .Nm rules. -If host is acting as a gateway, packets forwarded by -the gateway are processed by +The number of times a packet is processed by .Nm -twice. -In case a host is acting as a bridge, packets forwarded by -the bridge are processed by +varies -- basically, .Nm -once. +is invoked every time the kernel functions +.Em ip_input() , ip_output() +and +.Em bdg_forward() +are invoked. +This means that packets are processed once for connections having +only one endpoint on the local host, twice for connections with +both endpoints on the local host, or for packet routed by the host +(acting as a gateway), and once for packets bridged by the host +(acting as a bridge). .Pp A firewall configuration is made of a list of numbered rules, which is scanned for each packet until a match is found and @@ -90,8 +96,8 @@ way as to minimize the number of checks. .Pp A configuration always includes a .Em DEFAULT -rule (numbered 65535) which cannot be modified by the programmer -and always matches packets. +rule (numbered 65535) which cannot be modified, +and matches all packets. The action associated with the default rule can be either .Cm deny or @@ -100,11 +106,13 @@ depending on how the kernel is configured. .Pp If the ruleset includes one or more rules with the .Cm keep-state +or +.Cm limit option, then .Nm assumes a .Em stateful -behaviour, i.e. upon a match will create dynamic rules matching +behaviour, i.e. upon a match it will create dynamic rules matching the exact parameters (addresses and ports) of the matching packet. .Pp These dynamic rules, which have a limited lifetime, are checked @@ -632,7 +640,7 @@ while packets destined for the local host have no transmit interface. .It Ar options : .Bl -tag -width indent -.It Cm keep-state Op Ar method +.It Cm keep-state Upon a match, the firewall will create a dynamic rule, whose default behaviour is to matching bidirectional traffic between source and destination IP/port using the same protocol. @@ -641,9 +649,11 @@ The rule has a limited lifetime (controlled by a set of variables), and the lifetime is refreshed every time a matching packet is found. .Pp -The actual behaviour can be modified by specifying a different -.Ar method , -although at the moment only the default one is specified. +.It Cm limit {src-addr src-port dst-addr dst-port} N +The firewall will only allow N connections with the same +set of parameters as specified in the rule. One or more +of source and destination addresses and ports can be +specified. .It Cm bridged Matches only bridged packets. This can be useful for multicast or broadcast traffic, which @@ -1118,8 +1128,10 @@ dropped. A set of .Xr sysctl 8 variables controls the behaviour of the firewall. -These are shown below together with their default value and -meaning: +These are shown below together with their default value +(but always check with the +.Nm sysctl +command what value is actually in use) and meaning: .Bl -tag -width indent .It Em net.inet.ip.fw.debug : No 1 Controls debugging messages produced by @@ -1156,8 +1168,9 @@ When you hit this limit, no more dynamic rules can be installed until old ones expire. .It Em net.inet.ip.fw.dyn_ack_lifetime : No 300 .It Em net.inet.ip.fw.dyn_syn_lifetime : No 20 -.It Em net.inet.ip.fw.dyn_fin_lifetime : No 20 -.It Em net.inet.ip.fw.dyn_rst_lifetime : No 5 +.It Em net.inet.ip.fw.dyn_fin_lifetime : No 1 +.It Em net.inet.ip.fw.dyn_rst_lifetime : No 1 +.It Em net.inet.ip.fw.dyn_udp_lifetime : No 5 .It Em net.inet.ip.fw.dyn_short_lifetime : No 30 These variables control the lifetime, in seconds, of dynamic rules. @@ -1218,6 +1231,17 @@ rule should be usually placed near the beginning of the ruleset to minimize the amount of work scanning the ruleset. Your mileage may vary. .Pp +To limit the number of connections a user can open +you can use the following type of rules: +.Pp +.Dl "ipfw add allow tcp from my-net/24 to any setup limit src-addr 10" +.Dl "ipfw add allow tcp from any to me setup limit src-addr 4" +.Pp +The former (assuming it runs on a gateway) will allow each host +on a /24 net to open at most 10 TCP connections. +The latter can be placed on a server to make sure that a single +client does not use more than 4 simultaneous connections. +.Pp .Em BEWARE : stateful rules can be subject to denial-of-service attacks by a SYN-flood which opens a huge number of dynamic rules. diff --git a/sbin/ipfw/ipfw.c b/sbin/ipfw/ipfw.c index ba5d36c..c7fa1d5 100644 --- a/sbin/ipfw/ipfw.c +++ b/sbin/ipfw/ipfw.c @@ -168,6 +168,18 @@ print_reject_code(int code) printf("%u", code); } +/** + * _s_x holds a string-int pair for various lookups. + * s=NULL terminates the struct. + */ +struct _s_x { char *s; int x; }; +static struct _s_x limit_masks[] = { + {"src-addr", DYN_SRC_ADDR}, + {"src-port", DYN_SRC_PORT}, + {"dst-addr", DYN_DST_ADDR}, + {"dst-port", DYN_DST_PORT}, + {NULL, 0} }; + static void show_ipfw(struct ip_fw *chain) { @@ -204,7 +216,7 @@ show_ipfw(struct ip_fw *chain) } if (chain->fw_flg & IP_FW_F_RND_MATCH) { - double d = 1.0 * (int)(chain->pipe_ptr); + double d = 1.0 * chain->dont_match_prob; d = 1 - (d / 0x7fffffff); printf("prob %f ", d); } @@ -373,16 +385,22 @@ show_ipfw(struct ip_fw *chain) } if (chain->fw_flg & IP_FW_F_KEEP_S) { - u_long x = (u_long)chain->next_rule_ptr; - u_char type = (x) & 0xff ; + struct _s_x *p = limit_masks; - switch(type) { + switch(chain->dyn_type) { default: printf(" *** unknown type ***"); break ; case DYN_KEEP_STATE: printf(" keep-state"); break; + case DYN_LIMIT: + printf(" limit"); + for ( ; p->s != NULL ; p++) + if (chain->limit_mask & p->x) + printf(" %s", p->s); + printf(" %d", chain->conn_limit); + break ; } } /* Direction */ @@ -573,11 +591,17 @@ show_dyn_ipfw(struct ipfw_dyn_rule *d) return; printf("%05d %qu %qu (T %ds, slot %d)", - (int)(d->chain), + (int)(d->rule), d->pcnt, d->bcnt, d->expire, d->bucket); switch (d->dyn_type) { + case DYN_LIMIT_PARENT: + printf(" PARENT %d", d->count); + break; + case DYN_LIMIT: + printf(" LIMIT"); + break; case DYN_KEEP_STATE: /* bidir, no mask */ printf(" <->"); break; @@ -589,17 +613,10 @@ show_dyn_ipfw(struct ipfw_dyn_rule *d) printf(" %u,", d->id.proto); a.s_addr = htonl(d->id.src_ip); - printf(" %s", inet_ntoa(a)); - printf(" %d", d->id.src_port); + printf(" %si %d", inet_ntoa(a), d->id.src_port); - 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("<-> %s %d", inet_ntoa(a), d->id.dst_port); printf("\n"); } @@ -864,9 +881,9 @@ list(int ac, char *av[]) /* already warned */ continue; for (n = 0, d = dynrules; n < ndyn; n++, d++) { - if ((int)(d->chain) > rnum) + if ((int)(d->rule) > rnum) break; - if ((int)(d->chain) == rnum) + if ((int)(d->rule) == rnum) show_dyn_ipfw(d); } } @@ -1714,8 +1731,7 @@ add(int ac, char *av[]) 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); + rule.dont_match_prob = (long)((1 - d) * 0x7fffffff); } av += 2; ac -= 2; } @@ -1976,13 +1992,39 @@ add(int ac, char *av[]) } else if (!strncmp(*av, "in", strlen(*av))) { rule.fw_flg |= IP_FW_F_IN; av++; ac--; + } else if (!strncmp(*av,"limit",strlen(*av))) { + /* keep-state rules used to limit number of connections. */ + rule.fw_flg |= IP_FW_F_KEEP_S; + rule.dyn_type = DYN_LIMIT ; + rule.limit_mask = 0 ; + av++; ac--; + for (; ac >1 ;) { + struct _s_x *p = limit_masks; + int found = 0; + for ( ; p->s != NULL ; p++) + if (!strncmp(*av, p->s, strlen(*av))) { + rule.limit_mask |= p->x ; + av++; ac-- ; + } + if (found == 0) { + if (rule.limit_mask == 0) + errx(EX_USAGE, "missing limit mask"); + break ; + } + } + if (ac < 1) + errx(EX_USAGE, "limit needs mask and # of connections"); + rule.conn_limit = atoi(*av); + if (rule.conn_limit == 0) + errx(EX_USAGE, "limit: limit must be >0"); + 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; + rule.dyn_type = type; av++; ac--; } } else if (!strncmp(*av, "bridged", strlen(*av))) { -- cgit v1.1