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:
parent
645964e2a2
commit
03c612662b
@ -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 ;
|
||||
/*
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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 */
|
||||
|
Loading…
x
Reference in New Issue
Block a user