summaryrefslogtreecommitdiffstats
path: root/sys/netinet
diff options
context:
space:
mode:
authorluigi <luigi@FreeBSD.org>2000-02-10 14:17:40 +0000
committerluigi <luigi@FreeBSD.org>2000-02-10 14:17:40 +0000
commit0a7657b3329286c1adb3f581bd671054fb7b0636 (patch)
treed6950c94c1ae4f771f218f381f1e033d31bd5973 /sys/netinet
parent8ee757716a386e49c12c6c2880528590059b4261 (diff)
downloadFreeBSD-src-0a7657b3329286c1adb3f581bd671054fb7b0636.zip
FreeBSD-src-0a7657b3329286c1adb3f581bd671054fb7b0636.tar.gz
Support for stateful (dynamic) ipfw rules. They are very
similar to ipfilter's keep-state. Look at the updated ipfw(8) manpage for details. Approved-by: jordan
Diffstat (limited to 'sys/netinet')
-rw-r--r--sys/netinet/ip_dummynet.c66
-rw-r--r--sys/netinet/ip_dummynet.h14
-rw-r--r--sys/netinet/ip_fw.c471
-rw-r--r--sys/netinet/ip_fw.h39
4 files changed, 525 insertions, 65 deletions
diff --git a/sys/netinet/ip_dummynet.c b/sys/netinet/ip_dummynet.c
index 911a8b4..be409a9 100644
--- a/sys/netinet/ip_dummynet.c
+++ b/sys/netinet/ip_dummynet.c
@@ -70,13 +70,6 @@
#endif
/*
- * the addresses/ports of last pkt matched by the firewall are
- * in this structure. This is so that we can easily find them without
- * navigating through the mbuf.
- */
-struct dn_flow_id dn_last_pkt ;
-
-/*
* we keep a private variable for the simulation time, but probably
* it would be better to use the already existing one "softticks"
* (in sys/kern/kern_timer.c)
@@ -87,6 +80,7 @@ static int dn_hash_size = 64 ; /* default hash size */
/* statistics on number of queue searches and search steps */
static int searches, search_steps ;
+static int pipe_expire = 1 ; /* expire queue if empty */
static struct dn_heap ready_heap, extract_heap ;
static int heap_init(struct dn_heap *h, int size) ;
@@ -101,7 +95,7 @@ static struct dn_pipe *all_pipes = NULL ; /* list of all pipes */
SYSCTL_NODE(_net_inet_ip, OID_AUTO, dummynet,
CTLFLAG_RW, 0, "Dummynet");
SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, hash_size,
- CTLFLAG_RD, &dn_hash_size, 0, "Default hash table size");
+ CTLFLAG_RW, &dn_hash_size, 0, "Default hash table size");
SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, curr_time,
CTLFLAG_RD, &curr_time, 0, "Current tick");
SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, ready_heap,
@@ -112,6 +106,8 @@ SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, searches,
CTLFLAG_RD, &searches, 0, "Number of queue searches");
SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, search_steps,
CTLFLAG_RD, &search_steps, 0, "Number of queue search steps");
+SYSCTL_INT(_net_inet_ip_dummynet, OID_AUTO, expire,
+ CTLFLAG_RW, &pipe_expire, 0, "Expire queue if empty");
#endif
static int ip_dn_ctl(struct sockopt *sopt);
@@ -141,7 +137,7 @@ rt_unref(struct rtentry *rt)
*
* In the heap, first node is element 0. Children of i are 2i+1 and 2i+2.
* Some macros help finding parent/children so we can optimize them.
- #
+ *
* heap_init() is called to expand the heap when needed.
* Increment size in blocks of 256 entries (which make one 4KB page)
* XXX failure to allocate a new element is a pretty bad failure
@@ -449,7 +445,7 @@ dummynet(void * __unused unused)
}
/*
- * Given a pipe and a pkt in dn_last_pkt, find a matching queue
+ * Given a pipe and a pkt in last_pkt, find a matching queue
* after appropriate masking. The queue is moved to front
* so that further searches take less time.
* XXX if the queue is longer than some threshold should consider
@@ -466,25 +462,39 @@ find_queue(struct dn_pipe *pipe)
q = pipe->rq[0] ;
else {
/* first, do the masking */
- dn_last_pkt.dst_ip &= pipe->flow_mask.dst_ip ;
- dn_last_pkt.src_ip &= pipe->flow_mask.src_ip ;
- dn_last_pkt.dst_port &= pipe->flow_mask.dst_port ;
- dn_last_pkt.src_port &= pipe->flow_mask.src_port ;
- dn_last_pkt.proto &= pipe->flow_mask.proto ;
+ last_pkt.dst_ip &= pipe->flow_mask.dst_ip ;
+ last_pkt.src_ip &= pipe->flow_mask.src_ip ;
+ last_pkt.dst_port &= pipe->flow_mask.dst_port ;
+ last_pkt.src_port &= pipe->flow_mask.src_port ;
+ last_pkt.proto &= pipe->flow_mask.proto ;
/* then, hash function */
- i = ( (dn_last_pkt.dst_ip) & 0xffff ) ^
- ( (dn_last_pkt.dst_ip >> 15) & 0xffff ) ^
- ( (dn_last_pkt.src_ip << 1) & 0xffff ) ^
- ( (dn_last_pkt.src_ip >> 16 ) & 0xffff ) ^
- (dn_last_pkt.dst_port << 1) ^ (dn_last_pkt.src_port) ^
- (dn_last_pkt.proto );
+ i = ( (last_pkt.dst_ip) & 0xffff ) ^
+ ( (last_pkt.dst_ip >> 15) & 0xffff ) ^
+ ( (last_pkt.src_ip << 1) & 0xffff ) ^
+ ( (last_pkt.src_ip >> 16 ) & 0xffff ) ^
+ (last_pkt.dst_port << 1) ^ (last_pkt.src_port) ^
+ (last_pkt.proto );
i = i % pipe->rq_size ;
/* finally, scan the current list for a match */
searches++ ;
- for (prev=NULL, q = pipe->rq[i] ; q ; prev = q , q = q->next ) {
+ for (prev=NULL, q = pipe->rq[i] ; q ; ) {
search_steps++;
- if (bcmp(&dn_last_pkt, &(q->id), sizeof(q->id) ) == 0)
+ if (bcmp(&last_pkt, &(q->id), sizeof(q->id) ) == 0)
break ; /* found */
+ else if (pipe_expire && q->r.head == NULL) {
+ /* entry is idle, expire it */
+ struct dn_flow_queue *old_q = q ;
+
+ if (prev != NULL)
+ prev->next = q = q->next ;
+ else
+ pipe->rq[i] = q = q->next ;
+ pipe->rq_elements-- ;
+ free(old_q, M_IPFW);
+ continue ;
+ }
+ prev = q ;
+ q = q->next ;
}
if (q && prev != NULL) { /* found and not in front */
prev->next = q->next ;
@@ -499,7 +509,7 @@ find_queue(struct dn_pipe *pipe)
return NULL ;
}
bzero(q, sizeof(*q) ); /* needed */
- q->id = dn_last_pkt ;
+ q->id = last_pkt ;
q->p = pipe ;
q->hash_slot = i ;
q->next = pipe->rq[i] ;
@@ -507,8 +517,8 @@ find_queue(struct dn_pipe *pipe)
pipe->rq_elements++ ;
DEB(printf("++ new queue (%d) for 0x%08x/0x%04x -> 0x%08x/0x%04x\n",
pipe->rq_elements,
- dn_last_pkt.src_ip, dn_last_pkt.src_port,
- dn_last_pkt.dst_ip, dn_last_pkt.dst_port); )
+ last_pkt.src_ip, last_pkt.src_port,
+ last_pkt.dst_ip, last_pkt.dst_port); )
}
return q ;
}
@@ -534,8 +544,8 @@ dummynet_io(int pipe_nr, int dir,
*/
DEB(printf("-- last_pkt dst 0x%08x/0x%04x src 0x%08x/0x%04x\n",
- dn_last_pkt.dst_ip, dn_last_pkt.dst_port,
- dn_last_pkt.src_ip, dn_last_pkt.src_port);)
+ last_pkt.dst_ip, last_pkt.dst_port,
+ last_pkt.src_ip, last_pkt.src_port);)
pipe_nr &= 0xffff ;
/*
diff --git a/sys/netinet/ip_dummynet.h b/sys/netinet/ip_dummynet.h
index dbfa8a2..a386960 100644
--- a/sys/netinet/ip_dummynet.h
+++ b/sys/netinet/ip_dummynet.h
@@ -96,22 +96,13 @@ struct dn_queue {
} ;
/*
- * Flow mask/flow id for each queue.
- */
-struct dn_flow_id {
- u_int32_t dst_ip, src_ip ;
- u_int16_t dst_port, src_port ;
- u_int8_t proto ;
-} ;
-
-/*
* We use per flow queues. Hashing is used to select the right slot,
* then we scan the list to match the flow-id.
* The pipe is shared as it is only a delay line and thus one is enough.
*/
struct dn_flow_queue {
struct dn_flow_queue *next ;
- struct dn_flow_id id ;
+ struct ipfw_flow_id id ;
struct dn_pipe *p ; /* parent pipe */
struct dn_queue r;
long numbytes ;
@@ -141,7 +132,7 @@ struct dn_pipe { /* a pipe */
int plr ; /* pkt loss rate (2^31-1 means 100%) */
struct dn_queue p ;
- struct dn_flow_id flow_mask ;
+ struct ipfw_flow_id flow_mask ;
int rq_size ;
int rq_elements ;
struct dn_flow_queue **rq ; /* array of rq_size entries */
@@ -153,7 +144,6 @@ MALLOC_DECLARE(M_IPFW);
typedef int ip_dn_ctl_t __P((struct sockopt *)) ;
extern ip_dn_ctl_t *ip_dn_ctl_ptr;
-extern struct dn_flow_id dn_last_pkt ;
void dn_rule_delete(void *r); /* used in ip_fw.c */
int dummynet_io(int pipe, int dir,
diff --git a/sys/netinet/ip_fw.c b/sys/netinet/ip_fw.c
index 62655af..b5a9166 100644
--- a/sys/netinet/ip_fw.c
+++ b/sys/netinet/ip_fw.c
@@ -2,6 +2,7 @@
* 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.
@@ -15,6 +16,10 @@
* $FreeBSD$
*/
+#define STATEFUL 1
+#define DEB(x)
+#define DDB(x) x
+
/*
* Implement IP packet firewall
*/
@@ -67,7 +72,8 @@ static int fw_verbose = 1;
#else
static int fw_verbose = 0;
#endif
-static int fw_one_pass = 1 ;
+int fw_one_pass = 1 ;
+int fw_enable = 1 ;
#ifdef IPFIREWALL_VERBOSE_LIMIT
static int fw_verbose_limit = IPFIREWALL_VERBOSE_LIMIT;
#else
@@ -75,6 +81,7 @@ static int fw_verbose_limit = 0;
#endif
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)
@@ -85,15 +92,91 @@ MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");
#ifdef SYSCTL_NODE
SYSCTL_DECL(_net_inet_ip);
SYSCTL_NODE(_net_inet_ip, OID_AUTO, fw, CTLFLAG_RW, 0, "Firewall");
-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, 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 divert(4)/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");
+
+#if STATEFUL
+/*
+ * 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 */
+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 ;
+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, 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_short_lifetime, CTLFLAG_RW,
+ &dyn_rst_lifetime, 0, "Lifetime of dyn. rules for other situations");
+#endif /* STATEFUL */
+
#endif
#define dprintf(a) do { \
@@ -450,6 +533,246 @@ ipfw_report(struct ip_fw *f, struct ip *ip,
}
}
+#if STATEFUL
+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 ;
+}
+
+#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 ;
+ 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 ;
+ } else {
+ prev = q ;
+ q = q->next ;
+ }
+ }
+ }
+}
+
+static struct ipfw_dyn_rule *
+lookup_dyn_rule(struct ipfw_flow_id *pkt)
+{
+ /*
+ * stateful ipfw extensions.
+ * Lookup into dynamic session queue
+ */
+ struct ipfw_dyn_rule *prev, *q, *old_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 ; ) {
+ switch (q->type) {
+ default: /* bidirectional rule, no masks */
+ if ( pkt->proto == q->id.proto) {
+ 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 (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);
+ continue ;
+ } else {
+ prev = q ;
+ q = q->next ;
+ }
+ }
+ return NULL ; /* clearly not found */
+found:
+ if (q != NULL) { /* redundant check! */
+ 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_fin_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:
+ /* reset or some invalid combination */
+ if ( (q->state & ((TH_RST << 8)|TH_RST)) == 0)
+ printf("invalid state: 0x%x\n", q->state);
+ q->expire = time_second + dyn_rst_lifetime ;
+ break ;
+ }
+ } else {
+ /* should do something for UDP and others... */
+ q->expire = time_second + dyn_short_lifetime ;
+ }
+ }
+ return q ;
+}
+
+/*
+ * Install state for a dynamic session.
+ */
+
+static void
+add_dyn_rule(struct ipfw_flow_id *id, struct ipfw_flow_id *mask,
+ 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)
+ free(ipfw_dyn_v, M_IPFW);
+ ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof r,
+ M_IPFW, M_DONTWAIT);
+ if (ipfw_dyn_v == NULL)
+ return ; /* failed ! */
+ bzero(ipfw_dyn_v, curr_dyn_buckets * sizeof r);
+ }
+ }
+ i = hash_packet(id);
+
+ r = malloc(sizeof *r, M_IPFW, M_DONTWAIT);
+ if (r == NULL) {
+ printf ("sorry cannot allocate state\n");
+ return ;
+ }
+ bzero (r, sizeof (*r) );
+
+ 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->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 ); )
+}
+
+/*
+ * 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
+ */
+static void
+install_state(struct ip_fw_chain *chain, struct ip **pip, struct ip *ip)
+{
+ struct ipfw_dyn_rule *q ;
+ u_long 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) ;
+ if (q != NULL) {
+ printf(" entry already present, done\n");
+ return ;
+ }
+ if (dyn_count >= dyn_max) /* try remove old ones... */
+ remove_dyn_rule(NULL, 0 /* expire */);
+ if (dyn_count >= dyn_max) {
+ printf(" Too many dynamic rules, sorry\n");
+ return ;
+ }
+ switch (type) {
+ default: /* bidir rule */
+ add_dyn_rule(&last_pkt, NULL, chain);
+ break ;
+ }
+ q = lookup_dyn_rule(&last_pkt) ; /* XXX this just sets the lifetime ... */
+}
+#endif /* STATEFUL */
+
/*
* 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
@@ -510,15 +833,19 @@ ip_fw_chk(struct ip **pip, int hlen,
struct sockaddr_in **next_hop)
{
struct ip_fw_chain *chain;
- struct ip_fw *rule = NULL;
+ struct ip_fw *f = NULL, *rule = NULL;
struct ip *ip = NULL ;
struct ifnet *const rif = (*m)->m_pkthdr.rcvif;
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 ; /* XXX */
+ u_int8_t proto= 0, flags = 0 ; /* XXX */
u_int16_t skipto;
+#if STATEFUL
+ int dyn_checked = 0 ; /* set after dyn.rules have been checked. */
+ struct ipfw_dyn_rule *q = NULL ;
+#endif
/* Grab and reset cookie */
skipto = *cookie;
*cookie = 0;
@@ -578,12 +905,17 @@ non_ip: ip = NULL ;
dst_ip = ip->ip_dst ;
src_ip = ip->ip_src ;
proto = ip->ip_p ;
+ /*
+ * warning - if offset != 0, port values are bogus.
+ * Not a problem for ipfw, but could be for dummynet.
+ */
switch (proto) {
case IPPROTO_TCP :
PULLUP_TO(hlen + 14);
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 :
@@ -595,23 +927,23 @@ non_ip: ip = NULL ;
case IPPROTO_ICMP:
PULLUP_TO(hlen + 2);
+ flags = ((struct icmp *)
+ ((u_int32_t *)ip + ip->ip_hl))->icmp_type ;
break ;
default :
src_port = dst_port = 0 ;
}
#undef PULLUP_TO
-#ifdef DUMMYNET
- dn_last_pkt.src_ip = ntohl(src_ip.s_addr) ;
- dn_last_pkt.dst_ip = ntohl(dst_ip.s_addr) ;
- dn_last_pkt.proto = proto ;
- dn_last_pkt.src_port = ntohs(src_port) ;
- dn_last_pkt.dst_port = ntohs(dst_port) ;
-#endif
+ 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;
@@ -642,13 +974,45 @@ non_ip: ip = NULL ;
}
}
+
for (; chain; chain = LIST_NEXT(chain, chain)) {
- register struct ip_fw * f ;
again:
f = chain->rule;
if (f->fw_number == IPFW_DEFAULT_RULE)
goto got_match ;
+#if STATEFUL
+ /*
+ * 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 ;
+ if (ip)
+ q = lookup_dyn_rule(&last_pkt);
+ if (q != NULL) {
+ DEB(printf("-- dynamic match 0x%08x %d -> 0x%08x %d\n",
+ (q->id.src_ip), (q->id.src_port),
+ (q->id.dst_ip), (q->id.dst_port) ); )
+ chain = q->chain ;
+ q->pcnt++ ;
+ if (ip)
+ q->bcnt += ip->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 ;
+ }
+#endif /* stateful ipfw */
+ /*
+ * Rule only valid for bridged packets, skip if this
+ * is not one of those (pip != NULL)
+ */
+ if (pip != NULL && f->fw_flg & IP_FW_BRIDGED )
+ continue ;
+
if (oif) {
/* Check direction outbound */
if (!(f->fw_flg & IP_FW_F_OUT))
@@ -881,6 +1245,17 @@ rnd_then_got_match:
random() < ((struct ip_fw_ext *)f)->dont_match_prob )
continue ;
got_match:
+#if STATEFUL /* stateful ipfw */
+ /*
+ * If have a dynamic match (q != NULL) set f to the right rule;
+ * else, if have keep-state, install a new dynamic entry.
+ * The packet info is in last_pkt.
+ */
+ if (q != NULL)
+ f = chain->rule ;
+ else if (f->fw_flg & IP_FW_F_KEEP_S)
+ install_state(chain, pip, ip);
+#endif
*flow_id = chain ; /* XXX set flow id */
/* Update statistics */
f->fw_pcnt += 1;
@@ -1047,6 +1422,8 @@ add_entry(struct ip_fw_head *chainptr, struct ip_fw *frwl)
bcopy(frwl, 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) ;
ftmp->fw_in_if.fu_via_if.name[FW_IFNLEN - 1] = '\0';
ftmp->fw_pcnt = 0L;
@@ -1111,6 +1488,9 @@ del_entry(struct ip_fw_head *chainptr, u_short number)
while (fcp && fcp->rule->fw_number == number) {
struct ip_fw_chain *next;
+#if STATEFUL
+ remove_dyn_rule(fcp, 1 /* force_delete */);
+#endif
next = LIST_NEXT(fcp, chain);
LIST_REMOVE(fcp, chain);
#ifdef DUMMYNET
@@ -1244,6 +1624,10 @@ check_ipfw_struct(struct ip_fw *frwl)
err_prefix, frwl->fw_flg));
return (EINVAL);
}
+ if (frwl->fw_flg == IP_FW_F_CHECK_S) {
+ printf("check dynamic rules...\n");
+ 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));
@@ -1382,6 +1766,16 @@ ip_fw_ctl(struct sockopt *sopt)
for (fcp = LIST_FIRST(&ip_fw_chain), size = 0; fcp;
fcp = LIST_NEXT(fcp, chain))
size += sizeof *fcp->rule;
+#if STATEFUL
+ if (ipfw_dyn_v) {
+ int i ;
+ struct ipfw_dyn_rule *p ;
+
+ for (i = 0 ; i < curr_dyn_buckets ; i++ )
+ for ( p = ipfw_dyn_v[i] ; p != NULL ; p = p->next )
+ size += sizeof(*p) ;
+ }
+#endif
buf = malloc(size, M_TEMP, M_WAITOK);
if (buf == 0) {
error = ENOBUFS;
@@ -1393,13 +1787,41 @@ ip_fw_ctl(struct sockopt *sopt)
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++;
}
+#if STATEFUL
+ 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 ;
+ }
+#endif
error = sooptcopyout(sopt, buf, size);
FREE(buf, M_TEMP);
break;
case IP_FW_FLUSH:
+#if STATEFUL
+ s = splnet();
+ remove_dyn_rule(NULL, 1 /* force delete */);
+ splx(s);
+#endif
for (fcp = ip_fw_chain.lh_first;
fcp != 0 && fcp->rule->fw_number != IPFW_DEFAULT_RULE;
fcp = ip_fw_chain.lh_first) {
@@ -1499,17 +1921,19 @@ ip_fw_init(void)
ip_fw_default_rule = ip_fw_chain.lh_first ;
printf("IP packet filtering initialized, "
#ifdef IPDIVERT
- "divert enabled, ");
+ "divert enabled, "
#else
- "divert disabled, ");
+ "divert disabled, "
#endif
#ifdef IPFIREWALL_FORWARD
- printf("rule-based forwarding enabled, ");
+ "rule-based forwarding enabled, "
#else
- printf("rule-based forwarding disabled, ");
+ "rule-based forwarding disabled, "
#endif
#ifdef IPFIREWALL_DEFAULT_TO_ACCEPT
- printf("default to accept, ");
+ "default to accept, ");
+#else
+ "default to deny, " );
#endif
#ifndef IPFIREWALL_VERBOSE
printf("logging disabled\n");
@@ -1542,18 +1966,23 @@ ipfw_modevent(module_t mod, int type, void *unused)
return 0;
case MOD_UNLOAD:
s = splnet();
-
ip_fw_chk_ptr = old_chk_ptr;
ip_fw_ctl_ptr = old_ctl_ptr;
-
+#if STATEFUL
+ remove_dyn_rule(NULL, 1 /* force delete */);
+#endif
while (LIST_FIRST(&ip_fw_chain) != NULL) {
struct ip_fw_chain *fcp = LIST_FIRST(&ip_fw_chain);
LIST_REMOVE(LIST_FIRST(&ip_fw_chain), chain);
+#ifdef DUMMYNET
+ dn_rule_delete(fcp);
+#endif
free(fcp->rule, M_IPFW);
free(fcp, M_IPFW);
}
splx(s);
+ printf("IP firewall unloaded\n");
return 0;
default:
break;
diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h
index ffd1d04..a106108 100644
--- a/sys/netinet/ip_fw.h
+++ b/sys/netinet/ip_fw.h
@@ -102,7 +102,7 @@ struct ip_fw {
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 param1; /* unused at the moment */
+ u_int dyn_type; /* type for dynamic rule */
};
#define IP_FW_GETNSRCP(rule) ((rule)->fw_nports & 0x0f)
@@ -128,6 +128,33 @@ struct ip_fw_chain {
};
/*
+ * 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 */
+ 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) */
+} ;
+
+/*
* Values for "flags" field .
*/
#define IP_FW_F_COMMAND 0x000000ff /* Mask for type of chain entry: */
@@ -173,9 +200,11 @@ struct ip_fw_chain {
#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_F_KEEP_S 0x04000000 /* keep state */
+#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_MASK 0x03FFFFFF /* All possible flag bits mask */
+#define IP_FW_F_MASK 0x1FFFFFFF /* All possible flag bits mask */
/*
* For backwards compatibility with rules specifying "via iface" but
@@ -231,7 +260,9 @@ typedef int ip_fw_chk_t __P((struct ip **, int, struct ifnet *, u_int16_t *,
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 */
OpenPOWER on IntegriCloud