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
This commit is contained in:
Luigi Rizzo 2000-02-10 14:17:40 +00:00
parent 645964e2a2
commit 03c612662b
Notes: svn2git 2020-12-20 02:59:44 +00:00
svn path=/head/; revision=57113
4 changed files with 525 additions and 65 deletions

View File

@ -69,13 +69,6 @@
#include <net/bridge.h>
#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"
@ -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 ;
/*

View File

@ -95,15 +95,6 @@ struct dn_queue {
struct dn_pkt *head, *tail;
} ;
/*
* 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.
@ -111,7 +102,7 @@ struct dn_flow_id {
*/
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,

View File

@ -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 @@ non_ip: ip = NULL ;
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;

View File

@ -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)
@ -127,6 +127,33 @@ struct ip_fw_chain {
struct ip_fw *rule;
};
/*
* Flow mask/flow id for each queue.
*/
struct ipfw_flow_id {
u_int32_t dst_ip, src_ip ;
u_int16_t dst_port, src_port ;
u_int8_t proto ;
u_int8_t flags ; /* protocol-specific flags */
} ;
/*
* dynamic ipfw rule
*/
struct ipfw_dyn_rule {
struct ipfw_dyn_rule *next ;
struct ipfw_flow_id id ;
struct ipfw_flow_id mask ;
struct ip_fw_chain *chain ; /* pointer to parent rule */
u_int32_t type ; /* rule type */
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 .
*/
@ -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 */