From 830cc1784106d4ba64aeba573a14b7eee49d5798 Mon Sep 17 00:00:00 2001 From: Luigi Rizzo Date: Thu, 27 Sep 2001 23:44:27 +0000 Subject: [PATCH] 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 ++++++-- sys/net/bridge.c | 4 +- sys/netinet/ip_dummynet.c | 40 ++-- sys/netinet/ip_dummynet.h | 4 +- sys/netinet/ip_fw.c | 406 +++++++++++++++++++++++--------------- sys/netinet/ip_fw.h | 127 ++++++------ sys/netinet/ip_input.c | 4 +- sys/netinet/ip_output.c | 4 +- 9 files changed, 437 insertions(+), 290 deletions(-) diff --git a/sbin/ipfw/ipfw.8 b/sbin/ipfw/ipfw.8 index 16f2873e1433..475a7468e821 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 ba5d36c67130..c7fa1d52c3b2 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))) { diff --git a/sys/net/bridge.c b/sys/net/bridge.c index 690927774b8f..f63a751049aa 100644 --- a/sys/net/bridge.c +++ b/sys/net/bridge.c @@ -616,7 +616,7 @@ bdg_forward(struct mbuf *m0, struct ether_header *const eh, struct ifnet *dst) int once = 0; /* loop only once */ struct ifnet *real_dst = dst ; /* real dst from ether_output */ #ifdef IPFIREWALL - struct ip_fw_chain *rule = NULL ; /* did we match a firewall rule ? */ + struct ip_fw *rule = NULL ; /* did we match a firewall rule ? */ #endif /* @@ -631,7 +631,7 @@ bdg_forward(struct mbuf *m0, struct ether_header *const eh, struct ifnet *dst) #if defined(IPFIREWALL) && defined(DUMMYNET) if (m0->m_type == MT_DUMMYNET) { /* extract info from dummynet header */ - rule = (struct ip_fw_chain *)(m0->m_data) ; + rule = (struct ip_fw *)(m0->m_data) ; m0 = m0->m_next ; src = m0->m_pkthdr.rcvif; shared = 0 ; /* For sure this is our own mbuf. */ diff --git a/sys/netinet/ip_dummynet.c b/sys/netinet/ip_dummynet.c index 9e2da604783f..cdadd37cadad 100644 --- a/sys/netinet/ip_dummynet.c +++ b/sys/netinet/ip_dummynet.c @@ -162,10 +162,10 @@ void dummynet_drain(void); int if_tx_rdy(struct ifnet *ifp); /* - * ip_fw_chain is used when deleting a pipe, because ipfw rules can + * ip_fw_chain_head is used when deleting a pipe, because ipfw rules can * hold references to the pipe. */ -extern LIST_HEAD (ip_fw_head, ip_fw_chain) ip_fw_chain_head; +extern LIST_HEAD (ip_fw_head, ip_fw) ip_fw_chain_head; static void rt_unref(struct rtentry *rt) @@ -1005,11 +1005,11 @@ red_drops(struct dn_flow_set *fs, struct dn_flow_queue *q, int len) static __inline struct dn_flow_set * -locate_flowset(int pipe_nr, struct ip_fw_chain *rule) +locate_flowset(int pipe_nr, struct ip_fw *rule) { struct dn_flow_set *fs = NULL ; - if ( (rule->rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_QUEUE ) + if ( (rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_QUEUE ) for (fs=all_flow_sets; fs && fs->fs_nr != pipe_nr; fs=fs->next) ; else { @@ -1020,7 +1020,7 @@ locate_flowset(int pipe_nr, struct ip_fw_chain *rule) fs = &(p1->fs) ; } if (fs != NULL) - rule->rule->pipe_ptr = fs ; /* record for the future */ + rule->pipe_ptr = fs ; /* record for the future */ return fs ; } @@ -1032,7 +1032,7 @@ int dummynet_io(int pipe_nr, int dir, /* pipe_nr can also be a fs_nr */ struct mbuf *m, struct ifnet *ifp, struct route *ro, struct sockaddr_in *dst, - struct ip_fw_chain *rule, int flags) + struct ip_fw *rule, int flags) { struct dn_pkt *pkt; struct dn_flow_set *fs; @@ -1045,7 +1045,7 @@ dummynet_io(int pipe_nr, int dir, /* pipe_nr can also be a fs_nr */ pipe_nr &= 0xffff ; - if ( (fs = rule->rule->pipe_ptr) == NULL ) { + if ( (fs = rule->pipe_ptr) == NULL ) { fs = locate_flowset(pipe_nr, rule); if (fs == NULL) goto dropit ; /* this queue/pipe does not exist! */ @@ -1090,7 +1090,7 @@ dummynet_io(int pipe_nr, int dir, /* pipe_nr can also be a fs_nr */ /* ok, i can handle the pkt now... */ /* build and enqueue packet + parameters */ pkt->hdr.mh_type = MT_DUMMYNET ; - (struct ip_fw_chain *)pkt->hdr.mh_data = rule ; + (struct ip_fw *)pkt->hdr.mh_data = rule ; DN_NEXT(pkt) = NULL; pkt->dn_m = m; pkt->dn_dir = dir ; @@ -1126,7 +1126,7 @@ dummynet_io(int pipe_nr, int dir, /* pipe_nr can also be a fs_nr */ * to schedule it. This involves different actions for fixed-rate or * WF2Q queues. */ - if ( (rule->rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_PIPE ) { + if ( (rule->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_PIPE ) { /* * Fixed-rate queue: just insert into the ready_heap. */ @@ -1276,15 +1276,15 @@ static void dummynet_flush() { struct dn_pipe *curr_p, *p ; - struct ip_fw_chain *chain ; + struct ip_fw *rule ; struct dn_flow_set *fs, *curr_fs; int s ; s = splimp() ; /* remove all references to pipes ...*/ - LIST_FOREACH(chain, &ip_fw_chain_head, next) - chain->rule->pipe_ptr = NULL ; + LIST_FOREACH(rule, &ip_fw_chain_head, next) + rule->pipe_ptr = NULL ; /* prevent future matches... */ p = all_pipes ; all_pipes = NULL ; @@ -1313,7 +1313,7 @@ dummynet_flush() } -extern struct ip_fw_chain *ip_fw_default_rule ; +extern struct ip_fw *ip_fw_default_rule ; static void dn_rule_delete_fs(struct dn_flow_set *fs, void *r) { @@ -1635,7 +1635,7 @@ static int delete_pipe(struct dn_pipe *p) { int s ; - struct ip_fw_chain *chain ; + struct ip_fw *rule ; if (p->pipe_nr == 0 && p->fs.fs_nr == 0) return EINVAL ; @@ -1659,9 +1659,9 @@ delete_pipe(struct dn_pipe *p) else a->next = b->next ; /* remove references to this pipe from the ip_fw rules. */ - LIST_FOREACH(chain, &ip_fw_chain_head, next) - if (chain->rule->pipe_ptr == &(b->fs)) - chain->rule->pipe_ptr = NULL ; + LIST_FOREACH(rule, &ip_fw_chain_head, next) + if (rule->pipe_ptr == &(b->fs)) + rule->pipe_ptr = NULL ; /* remove all references to this pipe from flow_sets */ for (fs = all_flow_sets; fs; fs= fs->next ) @@ -1693,9 +1693,9 @@ delete_pipe(struct dn_pipe *p) else a->next = b->next ; /* remove references to this flow_set from the ip_fw rules. */ - LIST_FOREACH(chain, &ip_fw_chain_head, next) - if (chain->rule->pipe_ptr == b) - chain->rule->pipe_ptr = NULL ; + LIST_FOREACH(rule, &ip_fw_chain_head, next) + if (rule->pipe_ptr == b) + rule->pipe_ptr = NULL ; if (b->pipe != NULL) { /* Update total weight on parent pipe and cleanup parent heaps */ diff --git a/sys/netinet/ip_dummynet.h b/sys/netinet/ip_dummynet.h index fe80718c0688..279ae36acc18 100644 --- a/sys/netinet/ip_dummynet.h +++ b/sys/netinet/ip_dummynet.h @@ -343,14 +343,14 @@ struct dn_pipe { /* a pipe */ MALLOC_DECLARE(M_IPFW); -typedef int ip_dn_ctl_t __P((struct sockopt *)) ; +typedef int ip_dn_ctl_t (struct sockopt *) ; extern ip_dn_ctl_t *ip_dn_ctl_ptr; void dn_rule_delete(void *r); /* used in ip_fw.c */ int dummynet_io(int pipe, int dir, struct mbuf *m, struct ifnet *ifp, struct route *ro, struct sockaddr_in * dst, - struct ip_fw_chain *rule, int flags); + struct ip_fw *rule, int flags); #endif #endif /* _IP_DUMMYNET_H */ diff --git a/sys/netinet/ip_fw.c b/sys/netinet/ip_fw.c index cb21e5682f39..0ad3aae916d2 100644 --- a/sys/netinet/ip_fw.c +++ b/sys/netinet/ip_fw.c @@ -2,7 +2,7 @@ * Copyright (c) 1993 Daniel Boulet * Copyright (c) 1994 Ugen J.S.Antsilevich * Copyright (c) 1996 Alex Nash - * Copyright (c) 2000 Luigi Rizzo + * Copyright (c) 2000-2001 Luigi Rizzo * * Redistribution and use in source forms, with and without modification, * are permitted provided that this entire comment appears intact. @@ -92,7 +92,7 @@ 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; +LIST_HEAD (ip_fw_head, ip_fw) ip_fw_chain_head; MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's"); @@ -211,31 +211,30 @@ SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_grace_time, CTLFLAG_RD, } 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 *, int)); -static int check_ipfw_struct __P((struct ip_fw *m)); +static int add_entry (struct ip_fw_head *chainptr, struct ip_fw *frwl); +static int del_entry (struct ip_fw_head *chainptr, u_short number); +static int zero_entry (struct ip_fw *, int); +static int check_ipfw_struct (struct ip_fw *m); +static int iface_match (struct ifnet *ifp, union ip_fw_if *ifu, + int byname); +static int ipopts_match (struct ip *ip, struct ip_fw *f); +static int iptos_match (struct ip *ip, struct ip_fw *f); 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, + port_match (u_short *portptr, int nports, u_short port, + int range_flag, int mask); +static int tcpflg_match (struct tcphdr *tcp, struct ip_fw *f); +static int icmptype_match (struct icmp * icmp, struct ip_fw * f); +static void ipfw_report (struct ip_fw *f, struct ip *ip, int offset, int ip_len, struct ifnet *rif, - struct ifnet *oif)); + struct ifnet *oif); static void flush_rule_ptrs(void); -static int ip_fw_chk __P((struct ip **pip, int hlen, +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)); -static int ip_fw_ctl __P((struct sockopt *sopt)); + struct ip_fw **flow_id, + struct sockaddr_in **next_hop); +static int ip_fw_ctl (struct sockopt *sopt); static char err_prefix[] = "ip_fw_ctl:"; @@ -254,17 +253,14 @@ port_match(u_short *portptr, int nports, u_short port, int range_flag, int mask) portptr += 2; } if (range_flag) { - if (portptr[0] <= port && port <= portptr[1]) { + if (portptr[0] <= port && port <= portptr[1]) return 1; - } nports -= 2; portptr += 2; } - while (nports-- > 0) { - if (*portptr++ == port) { + while (nports-- > 0) + if (*portptr++ == port) return 1; - } - } return 0; } @@ -481,7 +477,7 @@ tcpopts_match(struct tcphdr *tcp, struct ip_fw *f) return 0; } -static __inline int +static int iface_match(struct ifnet *ifp, union ip_fw_if *ifu, int byname) { /* Check by name or by IP address */ @@ -683,7 +679,10 @@ hash_packet(struct ipfw_flow_id *id) #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", \ + /* remove a refcount to the parent */ \ + if (q->dyn_type == DYN_LIMIT) \ + q->parent->count--; \ + DEB(printf("-- unlink 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 ); ) \ if (prev != NULL) \ @@ -696,15 +695,15 @@ hash_packet(struct ipfw_flow_id *id) #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 + * Remove all dynamic rules pointing to a given rule, or all + * rules if rule == 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) +remove_dyn_rule(struct ip_fw *rule, int force) { struct ipfw_dyn_rule *prev, *q; - int i ; + int i, pass, max_pass ; static u_int32_t last_remove = 0 ; if (ipfw_dyn_v == NULL || dyn_count == 0) @@ -714,18 +713,32 @@ remove_dyn_rule(struct ip_fw_chain *chain, int force) return ; last_remove = time_second ; + /* + * because DYN_LIMIT refer to parent rules, during the first pass only + * remove child and mark any pending LIMIT_PARENT, and remove + * them in a second pass. + */ + for (pass = max_pass = 0; pass <= max_pass ; pass++ ) { for (i = 0 ; i < curr_dyn_buckets ; i++) { for (prev=NULL, q = ipfw_dyn_v[i] ; q ; ) { /* * logic can become complex here, so we split tests. - * First, test if we match any chain, + * First, test if we match any rule, * 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); + int zap = ( rule == NULL || rule == q->rule); if (zap) zap = force || TIME_LEQ( q->expire , time_second ); - + /* do not zap parent in first pass, record we need a second pass */ + if (q->dyn_type == DYN_LIMIT_PARENT) { + max_pass = 1; /* we need a second pass */ + if (zap == 1 && (pass == 0 || q->count != 0) ) { + zap = 0 ; + if (q->count != 0) + printf("cannot remove parent, count %d\n", q->count); + } + } if (zap) { UNLINK_DYN_RULE(prev, ipfw_dyn_v[i], q); } else { @@ -734,11 +747,12 @@ remove_dyn_rule(struct ip_fw_chain *chain, int force) } } } + } } -#define EXPIRE_DYN_CHAIN(chain) remove_dyn_rule(chain, 0 /* expired ones */) +#define EXPIRE_DYN_CHAIN(rule) remove_dyn_rule(rule, 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_CHAIN(rule) remove_dyn_rule(rule, 1 /* force removal */) #define DELETE_DYN_CHAINS() remove_dyn_rule(NULL, 1 /* force removal */) /** @@ -759,9 +773,11 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction) 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 */ - UNLINK_DYN_RULE(prev, ipfw_dyn_v[i], q); - continue ; + if (q->dyn_type == DYN_LIMIT_PARENT) + goto next; + if (TIME_LEQ( q->expire , time_second ) ) { /* expire entry */ + UNLINK_DYN_RULE(prev, ipfw_dyn_v[i], q); + continue ; } if ( pkt->proto == q->id.proto) { if (pkt->src_ip == q->id.src_ip && @@ -779,6 +795,7 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction) goto found ; } } +next: prev = q ; q = q->next ; } @@ -834,13 +851,19 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction) return q ; } -/* - * Install state for a dynamic session. +/** + * Install state of type 'type' for a dynamic session. + * The hash table contains two type of rules: + * - regular rules (DYN_KEEP_STATE) + * - rules for sessions with limited number of sess per user + * (DYN_LIMIT). When they are created, the parent is + * increased by 1, and decreased on delete. In this case, + * the third parameter is the parent rule and not the chain. + * - "parent" rules for the above (DYN_LIMIT_PARENT). */ static struct ipfw_dyn_rule * -add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, - struct ip_fw_chain *chain) +add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule) { struct ipfw_dyn_rule *r ; @@ -871,40 +894,79 @@ add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, return NULL ; } + /* increase refcount on parent, and set pointer */ + if (dyn_type == DYN_LIMIT) { + struct ipfw_dyn_rule *parent = (struct ipfw_dyn_rule *)rule; + if ( parent->dyn_type != DYN_LIMIT_PARENT) + panic("invalid parent"); + parent->count++ ; + r->parent = parent ; + rule = parent->rule; + } + r->id = *id ; r->expire = time_second + dyn_syn_lifetime ; - r->chain = chain ; + r->rule = rule ; r->dyn_type = dyn_type ; r->pcnt = r->bcnt = 0 ; + r->count = 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", + DEB(printf("-- add entry 0x%08x %d -> 0x%08x %d, total %d\n", (r->id.src_ip), (r->id.src_port), (r->id.dst_ip), (r->id.dst_port), dyn_count ); ) return r; } +/** + * lookup dynamic parent rule using pkt and chain as search keys. + * If the lookup fails, then install one. + */ +static struct ipfw_dyn_rule * +lookup_dyn_parent(struct ipfw_flow_id *pkt, struct ip_fw *rule) +{ + struct ipfw_dyn_rule *q; + int i; + + if (ipfw_dyn_v) { + i = hash_packet( pkt ); + for (q = ipfw_dyn_v[i] ; q != NULL ; q=q->next) + if (q->dyn_type == DYN_LIMIT_PARENT && rule == q->rule && + pkt->proto == q->id.proto && + 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) { + q->expire = time_second + dyn_short_lifetime ; + DEB(printf("lookup_dyn_parent found 0x%p\n", q);) + return q; + } + } + return add_dyn_rule(pkt, DYN_LIMIT_PARENT, rule); +} + /* * Install dynamic state. * There are different types of dynamic rules which can be installed. - * The type is in chain->dyn_type. + * The type is in rule->dyn_type. * Type 0 (default) is a bidirectional rule * - * Returns 1 (failure) if state is not installed. + * Returns 1 (failure) if state is not installed because of errors or because + * session limitations are enforced. */ static int -install_state(struct ip_fw_chain *chain) +install_state(struct ip_fw *rule) { struct ipfw_dyn_rule *q ; static int last_log ; - u_int8_t type = ((struct ip_fw_ext *)chain->rule)->dyn_type ; + u_int8_t type = rule->dyn_type ; - DEB(printf("-- install state type %d 0x%08lx %u -> 0x%08lx %u\n", + DEB(printf("-- install state type %d 0x%08x %u -> 0x%08x %u\n", type, (last_pkt.src_ip), (last_pkt.src_port), (last_pkt.dst_ip), (last_pkt.dst_port) );) @@ -927,34 +989,73 @@ install_state(struct ip_fw_chain *chain) return 1; /* cannot install, notify caller */ } switch (type) { - default: /* bidir rule */ - add_dyn_rule(&last_pkt, DYN_KEEP_STATE, chain); - break ; + case DYN_KEEP_STATE: /* bidir rule */ + add_dyn_rule(&last_pkt, DYN_KEEP_STATE, rule); + break ; + case DYN_LIMIT: /* limit number of sessions */ + { + u_int16_t limit_mask = rule->limit_mask ; + u_int16_t conn_limit = rule->conn_limit ; + struct ipfw_flow_id id; + struct ipfw_dyn_rule *parent; + + DEB(printf("installing dyn-limit rule %d\n", conn_limit);) + + id.dst_ip = id.src_ip = 0; + id.dst_port = id.src_port = 0 ; + id.proto = last_pkt.proto ; + + if (limit_mask & DYN_SRC_ADDR) + id.src_ip = last_pkt.src_ip; + if (limit_mask & DYN_DST_ADDR) + id.dst_ip = last_pkt.dst_ip; + if (limit_mask & DYN_SRC_PORT) + id.src_port = last_pkt.src_port; + if (limit_mask & DYN_DST_PORT) + id.dst_port = last_pkt.dst_port; + parent = lookup_dyn_parent(&id, rule); + if (parent == NULL) { + printf("add parent failed\n"); + return 1; + } + if (parent->count >= conn_limit) { + EXPIRE_DYN_CHAIN(rule); /* try to expire some */ + if (parent->count >= conn_limit) { + printf("drop session, too many entries\n"); + return 1; + } + } + add_dyn_rule(&last_pkt, DYN_LIMIT, (struct ip_fw *)parent); + } + break ; + default: + printf("unknown dynamic rule type %u\n", type); + return 1 ; } 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 + * given an ip_fw *, 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 * lookup_next_rule(struct ip_fw *me); -static struct ip_fw_chain * -lookup_next_rule(struct ip_fw_chain *me) +static struct ip_fw * +lookup_next_rule(struct ip_fw *me) { - struct ip_fw_chain *chain ; - int rule = me->rule->fw_skipto_rule ; /* guess... */ + struct ip_fw *rule ; + int rulenum = me->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 ; + if ( (me->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_SKIPTO ) + for (rule = LIST_NEXT(me,next); rule ; rule = LIST_NEXT(rule,next)) + if (rule->fw_number >= rulenum) + return rule ; return LIST_NEXT(me,next) ; /* failure or not a skipto */ } @@ -989,14 +1090,14 @@ lookup_next_rule(struct ip_fw_chain *me) 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 ip_fw **flow_id, struct sockaddr_in **next_hop) { - struct ip_fw_chain *chain; - struct ip_fw *f = NULL, *rule = NULL; + struct ip_fw *f = NULL; /* matching rule */ 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 */ @@ -1085,38 +1186,37 @@ ip_fw_chk(struct ip **pip, int hlen, last_pkt.flags = flags; if (*flow_id) { - /* - * Packet has already been tagged. Look for the next rule - * to restart processing. - */ - if (fw_one_pass) /* just accept if fw_one_pass is set */ - return 0; + /* + * Packet has already been tagged. Look for the next rule + * to restart processing. + */ + if (fw_one_pass) /* just accept if fw_one_pass is set */ + return 0; - 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; + f = (*flow_id)->next_rule_ptr ; + if (f == NULL) + f = (*flow_id)->next_rule_ptr = lookup_next_rule(*flow_id); + if (f == 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; - } + /* + * Go down the chain, looking for enlightment. + * If we've been asked to start at a given rule, do so. + */ + f = LIST_FIRST(&ip_fw_chain_head); + if (skipto != 0) { + if (skipto >= IPFW_DEFAULT_RULE) + goto dropit; + while (f && f->fw_number <= skipto) + f = LIST_NEXT(f, next); + if (f == NULL) + goto dropit; + } } - for (; chain; chain = LIST_NEXT(chain, next)) { + for (; f; f = LIST_NEXT(f, next)) { again: - f = chain->rule; if (f->fw_number == IPFW_DEFAULT_RULE) goto got_match ; @@ -1133,8 +1233,7 @@ ip_fw_chk(struct ip **pip, int hlen, (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 ; + f = q->rule ; q->pcnt++ ; q->bcnt += ip_len; goto got_match ; /* random not allowed here */ @@ -1308,7 +1407,7 @@ ip_fw_chk(struct ip **pip, int hlen, * packet -- if this rule specified either one, * we consider the rule a non-match. */ - if (f->fw_nports != 0 || + if (IP_FW_HAVEPORTS(f) != 0 || f->fw_ipflg & IP_FW_IF_TCPMSK) continue; @@ -1338,7 +1437,7 @@ ip_fw_chk(struct ip **pip, int hlen, * rule specifies a port, we consider the rule * a non-match. */ - if (f->fw_nports != 0) + if (IP_FW_HAVEPORTS(f) ) continue; break; @@ -1379,8 +1478,7 @@ ip_fw_chk(struct ip **pip, int hlen, } rnd_then_got_match: - if ( ((struct ip_fw_ext *)f)->dont_match_prob && - random() < ((struct ip_fw_ext *)f)->dont_match_prob ) + if ( f->dont_match_prob && random() < f->dont_match_prob ) continue ; got_match: /* @@ -1388,7 +1486,7 @@ ip_fw_chk(struct ip **pip, int hlen, * a new dynamic entry. */ if (q == NULL && f->fw_flg & IP_FW_F_KEEP_S) { - if (install_state(chain)) /* error or limit violation */ + if (install_state(f)) /* error or limit violation */ goto dropit; } /* Update statistics */ @@ -1415,15 +1513,15 @@ ip_fw_chk(struct ip **pip, int hlen, return(f->fw_divert_port | IP_FW_PORT_TEE_FLAG); #endif case IP_FW_F_SKIPTO: /* XXX check */ - chain = f->next_rule_ptr ? f->next_rule_ptr : - lookup_next_rule(chain) ; - if (! chain) + f = f->next_rule_ptr ? f->next_rule_ptr : + lookup_next_rule(f) ; + if (!f) goto dropit; goto again ; #ifdef DUMMYNET case IP_FW_F_PIPE: case IP_FW_F_QUEUE: - *flow_id = chain; + *flow_id = f; return(f->fw_pipe_nr | IP_FW_PORT_DYNT_FLAG); #endif #ifdef IPFIREWALL_FORWARD @@ -1437,6 +1535,9 @@ ip_fw_chk(struct ip **pip, int hlen, * 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) + * XXX warning-- there is a dangerous reference here + * from next_hop to a field within the rule. If the + * rule is deleted, weird things might occur. */ if (next_hop != NULL /* Make sure, first... */ && (q == NULL || direction == MATCH_FORWARD) ) @@ -1446,13 +1547,11 @@ ip_fw_chk(struct ip **pip, int hlen, } /* 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")); + KASSERT(f != NULL, ("ip_fw: no chain")); /* * At this point, we're going to drop the packet. @@ -1462,11 +1561,11 @@ ip_fw_chk(struct ip **pip, int hlen, * - 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 + if ((f->fw_flg & IP_FW_F_COMMAND) == IP_FW_F_REJECT && (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) { + switch (f->fw_reject_code) { case IP_FW_REJECT_RST: { /* XXX warning, this code writes into the mbuf */ @@ -1497,7 +1596,7 @@ ip_fw_chk(struct ip **pip, int hlen, } default: /* Send an ICMP unreachable using code */ icmp_error(*m, ICMP_UNREACH, - rule->fw_reject_code, 0L, 0); + f->fw_reject_code, 0L, 0); *m = NULL; break; } @@ -1520,39 +1619,25 @@ ip_fw_chk(struct ip **pip, int hlen, static void flush_rule_ptrs() { - struct ip_fw_chain *fcp ; + struct ip_fw *fcp ; LIST_FOREACH(fcp, &ip_fw_chain_head, next) { - fcp->rule->next_rule_ptr = NULL ; + fcp->next_rule_ptr = NULL ; } } static int 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, *fcp, *fcpl; + struct ip_fw *ftmp, *fcp, *fcpl; u_short nbr = 0; int s; - fwc = malloc(sizeof *fwc, M_IPFW, M_DONTWAIT); - if (!fwc) + ftmp = malloc(sizeof *ftmp, M_IPFW, M_DONTWAIT | M_ZERO); + if (!ftmp) return (ENOSPC); - ftmp_ext = malloc(sizeof *ftmp_ext, M_IPFW, M_DONTWAIT | M_ZERO); - if (!ftmp_ext) { - free(fwc, M_IPFW); - return (ENOSPC); - } - fwc->rule = ftmp = &ftmp_ext->rule ; 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) { - 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; @@ -1563,15 +1648,15 @@ add_entry(struct ip_fw_head *head, struct ip_fw *rule) s = splnet(); if (LIST_FIRST(head) == 0) { - LIST_INSERT_HEAD(head, fwc, next); + LIST_INSERT_HEAD(head, ftmp, next); goto done; } /* If entry number is 0, find highest numbered rule and add 100 */ if (ftmp->fw_number == 0) { - LIST_FOREACH(fcp, head, next) { - if (fcp->rule->fw_number != IPFW_DEFAULT_RULE) - nbr = fcp->rule->fw_number; + LIST_FOREACH(ftmp, head, next) { + if (ftmp->fw_number != IPFW_DEFAULT_RULE) + nbr = ftmp->fw_number; else break; } @@ -1583,11 +1668,11 @@ add_entry(struct ip_fw_head *head, struct ip_fw *rule) /* Got a valid number; now insert it, keeping the list ordered */ fcpl = NULL ; LIST_FOREACH(fcp, head, next) { - if (fcp->rule->fw_number > ftmp->fw_number) { + if (fcp->fw_number > ftmp->fw_number) { if (fcpl) { - LIST_INSERT_AFTER(fcpl, fwc, next); + LIST_INSERT_AFTER(fcpl, ftmp, next); } else { - LIST_INSERT_HEAD(head, fwc, next); + LIST_INSERT_HEAD(head, ftmp, next); } break; } else { @@ -1610,10 +1695,10 @@ add_entry(struct ip_fw_head *head, struct ip_fw *rule) * @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) +static struct ip_fw * +free_chain(struct ip_fw *fcp) { - struct ip_fw_chain *n; + struct ip_fw *n; n = LIST_NEXT(fcp, next); DELETE_DYN_CHAIN(fcp); @@ -1623,7 +1708,6 @@ free_chain(struct ip_fw_chain *fcp) dn_rule_delete(fcp) ; #endif flush_rule_ptrs(); /* more efficient to do outside the loop */ - free(fcp->rule, M_IPFW); free(fcp, M_IPFW); return n; } @@ -1634,16 +1718,16 @@ free_chain(struct ip_fw_chain *fcp) static int del_entry(struct ip_fw_head *chainptr, u_short number) { - struct ip_fw_chain *fcp; + struct ip_fw *rule; if (number != IPFW_DEFAULT_RULE) { - LIST_FOREACH(fcp, chainptr, next) { - if (fcp->rule->fw_number == number) { + LIST_FOREACH(rule, chainptr, next) { + if (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); + while (rule && rule->fw_number == number) + rule = free_chain(rule); /* XXX could move flush_rule_ptrs() here */ splx(s); return 0 ; @@ -1663,7 +1747,6 @@ del_entry(struct ip_fw_head *chainptr, u_short number) static int zero_entry(struct ip_fw *frwl, int log_only) { - struct ip_fw_chain *fcp; struct ip_fw *rule; int s; u_short number = 0 ; @@ -1671,8 +1754,7 @@ zero_entry(struct ip_fw *frwl, int log_only) if (frwl == 0) { s = splnet(); - LIST_FOREACH(fcp, &ip_fw_chain_head, next) { - rule = fcp->rule; + LIST_FOREACH(rule, &ip_fw_chain_head, next) { if (log_only == 0) { rule->fw_bcnt = rule->fw_pcnt = 0; rule->timestamp = 0; @@ -1691,16 +1773,16 @@ zero_entry(struct ip_fw *frwl, int log_only) * 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) { + LIST_FOREACH(rule, &ip_fw_chain_head, next) + if (number == rule->fw_number) { s = splnet(); - while (fcp && number == (rule=fcp->rule)->fw_number) { + while (rule && number == 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); + rule = LIST_NEXT(rule, next); } splx(s); cleared = 1; @@ -1791,7 +1873,7 @@ check_ipfw_struct(struct ip_fw *frwl) if ((frwl->fw_flg & IP_FW_F_FRAG) && (frwl->fw_prot == IPPROTO_UDP || frwl->fw_prot == IPPROTO_TCP)) { - if (frwl->fw_nports) { + if (IP_FW_HAVEPORTS(frwl)) { dprintf(("%s cannot mix 'frag' and ports\n", err_prefix)); return (EINVAL); } @@ -1857,7 +1939,7 @@ ip_fw_ctl(struct sockopt *sopt) { int error, s; size_t size; - struct ip_fw_chain *fcp; + struct ip_fw *fcp; struct ip_fw frwl, *bp , *buf; /* @@ -1896,11 +1978,7 @@ ip_fw_ctl(struct sockopt *sopt) bp = buf ; LIST_FOREACH(fcp, &ip_fw_chain_head, next) { - 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); + bcopy(fcp, bp, sizeof *fcp); bp++; } if (ipfw_dyn_v) { @@ -1911,7 +1989,7 @@ ip_fw_ctl(struct sockopt *sopt) 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 ; + (int)dst->rule = p->rule->fw_number ; dst->next = dst ; /* fake non-null pointer... */ last = dst ; if (TIME_LEQ(dst->expire, time_second) ) @@ -1944,7 +2022,7 @@ ip_fw_ctl(struct sockopt *sopt) s = splnet(); while ( (fcp = LIST_FIRST(&ip_fw_chain_head)) && - fcp->rule->fw_number != IPFW_DEFAULT_RULE ) + fcp->fw_number != IPFW_DEFAULT_RULE ) free_chain(fcp); splx(s); break; @@ -2011,7 +2089,7 @@ ip_fw_ctl(struct sockopt *sopt) * 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 ; +struct ip_fw *ip_fw_default_rule ; void ip_fw_init(void) @@ -2070,7 +2148,7 @@ static int ipfw_modevent(module_t mod, int type, void *unused) { int s; - struct ip_fw_chain *fcp; + struct ip_fw *fcp; switch (type) { case MOD_LOAD: diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index a4d3f2446444..5590b96174ba 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -50,28 +50,59 @@ union ip_fw_if { */ 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 */ + LIST_ENTRY(ip_fw) next; /* bidirectional list of rules */ + u_int fw_flg; /* Operational Flags word */ + + u_int64_t fw_pcnt,fw_bcnt; /* Packet and byte counters */ + struct in_addr fw_src, fw_dst; /* Source and dest. IP addr */ + struct in_addr fw_smsk, fw_dmsk; /* Mask for above addresses */ + u_short fw_number; /* Rule number */ + u_char fw_prot; /* IP protocol */ +#if 1 + u_char fw_nports; /* # of src/dst port in array */ +#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 IP_FW_HAVEPORTS(rule) ((rule)->fw_nports != 0) +#else + u_char __pad[1]; +u_int _nsrcp, _ndstp; +#define IP_FW_GETNSRCP(rule) (rule)->_nsrcp +#define IP_FW_SETNSRCP(rule,n) (rule)->_nsrcp = n +#define IP_FW_GETNDSTP(rule) (rule)->_ndstp +#define IP_FW_SETNDSTP(rule,n) (rule)->_ndstp = n +#define IP_FW_HAVEPORTS(rule) ((rule)->_ndstp + (rule)->_nsrcp != 0) +#endif +#define IP_FW_MAX_PORTS 10 /* A reasonable maximum */ + union { + u_short fw_pts[IP_FW_MAX_PORTS]; /* 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 */ + } fw_uar; + + u_int fw_ipflg; /* IP flags word */ + + u_short fw_iplen, fw_ipid; /* IP length, identification */ + + u_char fw_ipopt,fw_ipnopt; /* IP options set/unset */ 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 */ + + u_int32_t fw_tcpseq, fw_tcpack; /* TCP sequence and acknowledgement */ long timestamp; /* timestamp (tv_sec) of last match */ union ip_fw_if fw_in_if, fw_out_if; /* Incoming and outgoing interfaces */ union { @@ -81,51 +112,29 @@ struct ip_fw { 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 */ + 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_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 DYN_KEEP_STATE 0 /* type for keep-state rules */ +#define DYN_LIMIT 1 /* type for limit connection rules */ +#define DYN_LIMIT_PARENT 2 /* parent entry for limit connection rules */ + /* following two fields are used to limit number of connections + * basing on either src,srcport,dst,dstport. + */ + u_char limit_mask ; /* mask type for limit rule, can have many */ +#define DYN_SRC_ADDR 0x1 +#define DYN_SRC_PORT 0x2 +#define DYN_DST_ADDR 0x4 +#define DYN_DST_PORT 0x8 + u_short conn_limit ; /* # of connections for limit rule */ }; -#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 @@ -134,21 +143,14 @@ struct ip_fw_ext { /* extended structure */ /** * - * chain_ptr -------------+ + * rule_ptr -------------+ * V * [ next.le_next ]---->[ next.le_next ]---- [ next.le_next ]---> * [ next.le_prev ]<----[ next.le_prev ]<----[ next.le_prev ]<--- - * +--[ rule ] +--[ rule ] +--[ rule ] - * | | | - * +->[ ] +->[ ] +->[ ] + * [ body ] [ body ] [ body ] * */ -struct ip_fw_chain { - LIST_ENTRY(ip_fw_chain) next; - struct ip_fw *rule; -}; - /* * Flow mask/flow id for each queue. */ @@ -166,7 +168,8 @@ struct ipfw_dyn_rule { struct ipfw_dyn_rule *next ; struct ipfw_flow_id id ; /* (masked) flow id */ - struct ip_fw_chain *chain ; /* pointer to chain */ + struct ip_fw *rule ; /* pointer to rule */ + struct ipfw_dyn_rule *parent ; /* pointer to parent rule */ u_int32_t expire ; /* expire time */ u_int64_t pcnt, bcnt; /* match counters */ u_int32_t bucket ; /* which bucket in hash table */ @@ -311,9 +314,9 @@ 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 *)); +typedef int ip_fw_chk_t (struct ip **, int, struct ifnet *, u_int16_t *, + struct mbuf **, struct ip_fw **, struct sockaddr_in **); +typedef int ip_fw_ctl_t (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; diff --git a/sys/netinet/ip_input.c b/sys/netinet/ip_input.c index d80c7d4f72cb..9c0615b94314 100644 --- a/sys/netinet/ip_input.c +++ b/sys/netinet/ip_input.c @@ -280,7 +280,7 @@ ip_input(struct mbuf *m) #ifdef IPDIVERT u_int32_t divert_info = 0; /* packet divert/tee info */ #endif - struct ip_fw_chain *rule = NULL; + struct ip_fw *rule = NULL; #ifdef PFIL_HOOKS struct packet_filter_hook *pfh; struct mbuf *m0; @@ -302,7 +302,7 @@ ip_input(struct mbuf *m) * rule. */ if (m->m_type == MT_DUMMYNET) { - rule = (struct ip_fw_chain *)(m->m_data) ; + rule = (struct ip_fw *)(m->m_data) ; m = m->m_next ; ip = mtod(m, struct ip *); hlen = IP_VHL_HL(ip->ip_vhl) << 2; diff --git a/sys/netinet/ip_output.c b/sys/netinet/ip_output.c index bd09521b1846..012a3c4d6912 100644 --- a/sys/netinet/ip_output.c +++ b/sys/netinet/ip_output.c @@ -143,7 +143,7 @@ ip_output(m0, opt, ro, flags, imo) #ifdef IPFIREWALL_FORWARD int fwd_rewrite_src = 0; #endif - struct ip_fw_chain *rule = NULL; + struct ip_fw *rule = NULL; #ifdef IPDIVERT /* Get and reset firewall cookie */ @@ -165,7 +165,7 @@ ip_output(m0, opt, ro, flags, imo) * processing was already done, and we need to go down. * Get parameters from the header. */ - rule = (struct ip_fw_chain *)(m->m_data) ; + rule = (struct ip_fw *)(m->m_data) ; opt = NULL ; ro = & ( ((struct dn_pkt *)m)->ro ) ; imo = NULL ;