diff options
author | luigi <luigi@FreeBSD.org> | 2000-02-10 14:17:40 +0000 |
---|---|---|
committer | luigi <luigi@FreeBSD.org> | 2000-02-10 14:17:40 +0000 |
commit | 0a7657b3329286c1adb3f581bd671054fb7b0636 (patch) | |
tree | d6950c94c1ae4f771f218f381f1e033d31bd5973 | |
parent | 8ee757716a386e49c12c6c2880528590059b4261 (diff) | |
download | FreeBSD-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
-rw-r--r-- | sys/netinet/ip_dummynet.c | 66 | ||||
-rw-r--r-- | sys/netinet/ip_dummynet.h | 14 | ||||
-rw-r--r-- | sys/netinet/ip_fw.c | 471 | ||||
-rw-r--r-- | sys/netinet/ip_fw.h | 39 |
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 */ |