Add locking.

o change timeout to MPSAFE callout
o restructure rule deletion to deal with locking requirements
o replace static buffer used for ipfw control operations with malloc'd storage

Sponsored by:	FreeBSD Foundation
This commit is contained in:
Sam Leffler 2003-09-17 00:56:50 +00:00
parent 91176902bc
commit 293941a556

View File

@ -102,13 +102,25 @@ static u_int32_t set_disable;
static int fw_verbose;
static int verbose_limit;
static struct callout_handle ipfw_timeout_h;
static struct callout ipfw_timeout;
#define IPFW_DEFAULT_RULE 65535
struct ip_fw_chain {
struct ip_fw *rules; /* list of rules */
struct ip_fw *reap; /* list of rules to reap */
struct mtx mtx; /* lock guarding rule list */
};
#define IPFW_LOCK_INIT(_chain) \
mtx_init(&(_chain)->mtx, "IPFW static rules", NULL, MTX_DEF)
#define IPFW_LOCK_DESTROY(_chain) mtx_destroy(&(_chain)->mtx)
#define IPFW_LOCK(_chain) mtx_lock(&(_chain)->mtx)
#define IPFW_UNLOCK(_chain) mtx_unlock(&(_chain)->mtx)
#define IPFW_LOCK_ASSERT(_chain) mtx_assert(&(_chain)->mtx, MA_OWNED)
/*
* list of rules for layer 3
*/
static struct ip_fw *layer3_chain;
static struct ip_fw_chain layer3_chain;
MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");
@ -174,6 +186,14 @@ static 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 struct mtx ipfw_dyn_mtx; /* mutex guarding dynamic rules */
#define IPFW_DYN_LOCK_INIT() \
mtx_init(&ipfw_dyn_mtx, "IPFW dynamic rules", NULL, MTX_DEF)
#define IPFW_DYN_LOCK_DESTROY() mtx_destroy(&ipfw_dyn_mtx)
#define IPFW_DYN_LOCK() mtx_lock(&ipfw_dyn_mtx)
#define IPFW_DYN_UNLOCK() mtx_unlock(&ipfw_dyn_mtx)
#define IPFW_DYN_LOCK_ASSERT() mtx_assert(&ipfw_dyn_mtx, MA_OWNED)
/*
* Timeouts for various events in handing dynamic rules.
*/
@ -395,6 +415,7 @@ iface_match(struct ifnet *ifp, ipfw_insn_if *cmd)
} else {
struct ifaddr *ia;
/* XXX lock? */
TAILQ_FOREACH(ia, &ifp->if_addrhead, ifa_link) {
if (ia->ifa_addr == NULL)
continue;
@ -704,6 +725,8 @@ remove_dyn_rule(struct ip_fw *rule, ipfw_dyn_rule *keep_me)
ipfw_dyn_rule *prev, *q;
int i, pass = 0, max_pass = 0;
IPFW_DYN_LOCK_ASSERT();
if (ipfw_dyn_v == NULL || dyn_count == 0)
return;
/* do not expire more than once per second, it is useless */
@ -760,7 +783,7 @@ next:
* lookup a dynamic rule.
*/
static ipfw_dyn_rule *
lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction,
lookup_dyn_rule_locked(struct ipfw_flow_id *pkt, int *match_direction,
struct tcphdr *tcp)
{
/*
@ -774,6 +797,8 @@ lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction,
int i, dir = MATCH_NONE;
ipfw_dyn_rule *prev, *q=NULL;
IPFW_DYN_LOCK_ASSERT();
if (ipfw_dyn_v == NULL)
goto done; /* not found */
i = hash_packet( pkt );
@ -878,9 +903,25 @@ done:
return q;
}
static ipfw_dyn_rule *
lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction,
struct tcphdr *tcp)
{
ipfw_dyn_rule *q;
IPFW_DYN_LOCK();
q = lookup_dyn_rule_locked(pkt, match_direction, tcp);
if (q == NULL)
IPFW_DYN_UNLOCK();
/* NB: return table locked when q is not NULL */
return q;
}
static void
realloc_dynamic_table(void)
{
IPFW_DYN_LOCK_ASSERT();
/*
* Try reallocation, make sure we have a power of 2 and do
* not allow more than 64k entries. In case of overflow,
@ -921,6 +962,8 @@ add_dyn_rule(struct ipfw_flow_id *id, u_int8_t dyn_type, struct ip_fw *rule)
ipfw_dyn_rule *r;
int i;
IPFW_DYN_LOCK_ASSERT();
if (ipfw_dyn_v == NULL ||
(dyn_count == 0 && dyn_buckets != curr_dyn_buckets)) {
realloc_dynamic_table();
@ -974,6 +1017,8 @@ lookup_dyn_parent(struct ipfw_flow_id *pkt, struct ip_fw *rule)
ipfw_dyn_rule *q;
int i;
IPFW_DYN_LOCK_ASSERT();
if (ipfw_dyn_v) {
i = hash_packet( pkt );
for (q = ipfw_dyn_v[i] ; q != NULL ; q=q->next)
@ -1011,13 +1056,16 @@ install_state(struct ip_fw *rule, ipfw_insn_limit *cmd,
(args->f_id.src_ip), (args->f_id.src_port),
(args->f_id.dst_ip), (args->f_id.dst_port) );)
q = lookup_dyn_rule(&args->f_id, NULL, NULL);
IPFW_DYN_LOCK();
q = lookup_dyn_rule_locked(&args->f_id, NULL, NULL);
if (q != NULL) { /* should never occur */
if (last_log != time_second) {
last_log = time_second;
printf("ipfw: install_state: entry already present, done\n");
}
IPFW_DYN_UNLOCK();
return 0;
}
@ -1032,6 +1080,7 @@ install_state(struct ip_fw *rule, ipfw_insn_limit *cmd,
last_log = time_second;
printf("ipfw: install_state: Too many dynamic rules\n");
}
IPFW_DYN_UNLOCK();
return 1; /* cannot install, notify caller */
}
@ -1077,6 +1126,7 @@ install_state(struct ip_fw *rule, ipfw_insn_limit *cmd,
log(LOG_SECURITY | LOG_DEBUG,
"drop session, too many entries\n");
}
IPFW_DYN_UNLOCK();
return 1;
}
}
@ -1085,9 +1135,11 @@ install_state(struct ip_fw *rule, ipfw_insn_limit *cmd,
break;
default:
printf("ipfw: unknown dynamic rule type %u\n", cmd->o.opcode);
IPFW_DYN_UNLOCK();
return 1;
}
lookup_dyn_rule(&args->f_id, NULL, NULL); /* XXX just set lifetime */
lookup_dyn_rule_locked(&args->f_id, NULL, NULL); /* XXX just set lifetime */
IPFW_DYN_UNLOCK();
return 0;
}
@ -1349,6 +1401,7 @@ ipfw_chk(struct ip_fw_args *args)
int pktlen;
int dyn_dir = MATCH_UNKNOWN;
ipfw_dyn_rule *q = NULL;
struct ip_fw_chain *chain = &layer3_chain;
if (m->m_flags & M_SKIP_FIREWALL)
return 0; /* accept */
@ -1436,6 +1489,7 @@ ipfw_chk(struct ip_fw_args *args)
args->f_id.dst_port = dst_port = ntohs(dst_port);
after_ip_checks:
IPFW_LOCK(chain); /* XXX expensive? can we run lock free? */
if (args->rule) {
/*
* Packet has already been tagged. Look for the next rule
@ -1445,8 +1499,10 @@ after_ip_checks:
* XXX should not happen here, but optimized out in
* the caller.
*/
if (fw_one_pass)
if (fw_one_pass) {
IPFW_UNLOCK(chain); /* XXX optimize */
return 0;
}
f = args->rule->next_rule;
if (f == NULL)
@ -1458,14 +1514,18 @@ after_ip_checks:
*/
int skipto = args->divert_rule;
f = layer3_chain;
f = chain->rules;
if (args->eh == NULL && skipto != 0) {
if (skipto >= IPFW_DEFAULT_RULE)
if (skipto >= IPFW_DEFAULT_RULE) {
IPFW_UNLOCK(chain);
return(IP_FW_PORT_DENY_FLAG); /* invalid */
}
while (f && f->rulenum <= skipto)
f = f->next;
if (f == NULL) /* drop packet */
if (f == NULL) { /* drop packet */
IPFW_UNLOCK(chain);
return(IP_FW_PORT_DENY_FLAG);
}
}
}
args->divert_rule = 0; /* reset to avoid confusion later */
@ -1549,6 +1609,7 @@ check_body:
} else
break;
/* XXX locking? */
pcb = (oif) ?
in_pcblookup_hash(pi,
dst_ip, htons(dst_port),
@ -1911,6 +1972,7 @@ check_body:
f = q->rule;
cmd = ACTION_PTR(f);
l = f->cmd_len - f->act_ofs;
IPFW_DYN_UNLOCK();
goto check_body;
}
/*
@ -2006,6 +2068,7 @@ next_rule:; /* try next rule */
} /* end of outer for, scan rules */
printf("ipfw: ouch!, skip past end of rules, denying packet\n");
IPFW_UNLOCK(chain);
return(IP_FW_PORT_DENY_FLAG);
done:
@ -2013,6 +2076,7 @@ done:
f->pcnt++;
f->bcnt += pktlen;
f->timestamp = time_second;
IPFW_UNLOCK(chain);
return retval;
pullup_failed:
@ -2024,28 +2088,29 @@ pullup_failed:
/*
* When a rule is added/deleted, clear the next_rule pointers in all rules.
* These will be reconstructed on the fly as packets are matched.
* Must be called at splimp().
*/
static void
flush_rule_ptrs(void)
flush_rule_ptrs(struct ip_fw_chain *chain)
{
struct ip_fw *rule;
for (rule = layer3_chain; rule; rule = rule->next)
IPFW_LOCK_ASSERT(chain);
for (rule = chain->rules; rule; rule = rule->next)
rule->next_rule = NULL;
}
/*
* When pipes/queues are deleted, clear the "pipe_ptr" pointer to a given
* pipe/queue, or to all of them (match == NULL).
* Must be called at splimp().
*/
void
flush_pipe_ptrs(struct dn_flow_set *match)
{
struct ip_fw *rule;
for (rule = layer3_chain; rule; rule = rule->next) {
IPFW_LOCK(&layer3_chain);
for (rule = layer3_chain.rules; rule; rule = rule->next) {
ipfw_insn_pipe *cmd = (ipfw_insn_pipe *)ACTION_PTR(rule);
if (cmd->o.opcode != O_PIPE && cmd->o.opcode != O_QUEUE)
@ -2060,6 +2125,7 @@ flush_pipe_ptrs(struct dn_flow_set *match)
!bcmp(&cmd->pipe_ptr, &match, sizeof(match)) )
bzero(&cmd->pipe_ptr, sizeof(cmd->pipe_ptr));
}
IPFW_UNLOCK(&layer3_chain);
}
/*
@ -2068,13 +2134,12 @@ flush_pipe_ptrs(struct dn_flow_set *match)
* Update the rule_number in the input struct so the caller knows it as well.
*/
static int
add_rule(struct ip_fw **head, struct ip_fw *input_rule)
add_rule(struct ip_fw_chain *chain, struct ip_fw *input_rule)
{
struct ip_fw *rule, *f, *prev;
int s;
int l = RULESIZE(input_rule);
if (*head == NULL && input_rule->rulenum != IPFW_DEFAULT_RULE)
if (chain->rules == NULL && input_rule->rulenum != IPFW_DEFAULT_RULE)
return (EINVAL);
rule = malloc(l, M_IPFW, M_NOWAIT | M_ZERO);
@ -2090,10 +2155,10 @@ add_rule(struct ip_fw **head, struct ip_fw *input_rule)
rule->bcnt = 0;
rule->timestamp = 0;
s = splimp();
IPFW_LOCK(chain);
if (*head == NULL) { /* default rule */
*head = rule;
if (chain->rules == NULL) { /* default rule */
chain->rules = rule;
goto done;
}
@ -2109,7 +2174,7 @@ add_rule(struct ip_fw **head, struct ip_fw *input_rule)
/*
* locate the highest numbered rule before default
*/
for (f = *head; f; f = f->next) {
for (f = chain->rules; f; f = f->next) {
if (f->rulenum == IPFW_DEFAULT_RULE)
break;
rule->rulenum = f->rulenum;
@ -2122,72 +2187,94 @@ add_rule(struct ip_fw **head, struct ip_fw *input_rule)
/*
* Now insert the new rule in the right place in the sorted list.
*/
for (prev = NULL, f = *head; f; prev = f, f = f->next) {
for (prev = NULL, f = chain->rules; f; prev = f, f = f->next) {
if (f->rulenum > rule->rulenum) { /* found the location */
if (prev) {
rule->next = f;
prev->next = rule;
} else { /* head insert */
rule->next = *head;
*head = rule;
rule->next = chain->rules;
chain->rules = rule;
}
break;
}
}
flush_rule_ptrs();
flush_rule_ptrs(chain);
done:
static_count++;
static_len += l;
splx(s);
IPFW_UNLOCK(chain);
DEB(printf("ipfw: installed rule %d, static count now %d\n",
rule->rulenum, static_count);)
return (0);
}
/**
* Free storage associated with a static rule (including derived
* dynamic rules).
* Remove a static rule (including derived * dynamic rules)
* and place it on the ``reap list'' for later reclamation.
* The caller is in charge of clearing rule pointers to avoid
* dangling pointers.
* @return a pointer to the next entry.
* Arguments are not checked, so they better be correct.
* Must be called at splimp().
*/
static struct ip_fw *
delete_rule(struct ip_fw **head, struct ip_fw *prev, struct ip_fw *rule)
remove_rule(struct ip_fw_chain *chain, struct ip_fw *rule, struct ip_fw *prev)
{
struct ip_fw *n;
int l = RULESIZE(rule);
IPFW_LOCK_ASSERT(chain);
n = rule->next;
IPFW_DYN_LOCK();
remove_dyn_rule(rule, NULL /* force removal */);
IPFW_DYN_UNLOCK();
if (prev == NULL)
*head = n;
chain->rules = n;
else
prev->next = n;
static_count--;
static_len -= l;
if (DUMMYNET_LOADED)
ip_dn_ruledel_ptr(rule);
free(rule, M_IPFW);
rule->next = chain->reap;
chain->reap = rule;
return n;
}
/*
* Deletes all rules from a chain (except rules in set RESVD_SET
* unless kill_default = 1).
* Must be called at splimp().
/**
* Reclaim storage associated with a list of rules. This is
* typically the list created using remove_rule.
*/
static void
free_chain(struct ip_fw **chain, int kill_default)
reap_rules(struct ip_fw *head)
{
struct ip_fw *rule;
while ((rule = head) != NULL) {
head = head->next;
if (DUMMYNET_LOADED)
ip_dn_ruledel_ptr(rule);
free(rule, M_IPFW);
}
}
/*
* Remove all rules from a chain (except rules in set RESVD_SET
* unless kill_default = 1). The caller is responsible for
* reclaiming storage for the rules left in chain->reap.
*/
static void
free_chain(struct ip_fw_chain *chain, int kill_default)
{
struct ip_fw *prev, *rule;
flush_rule_ptrs(); /* more efficient to do outside the loop */
for (prev = NULL, rule = *chain; rule ; )
IPFW_LOCK_ASSERT(chain);
flush_rule_ptrs(chain); /* more efficient to do outside the loop */
for (prev = NULL, rule = chain->rules; rule ; )
if (kill_default || rule->set != RESVD_SET)
rule = delete_rule(chain, prev, rule);
rule = remove_rule(chain, rule, prev);
else {
prev = rule;
rule = rule->next;
@ -2208,10 +2295,9 @@ free_chain(struct ip_fw **chain, int kill_default)
* 4 swap sets with given numbers
*/
static int
del_entry(struct ip_fw **chain, u_int32_t arg)
del_entry(struct ip_fw_chain *chain, u_int32_t arg)
{
struct ip_fw *prev = NULL, *rule = *chain;
int s;
struct ip_fw *prev = NULL, *rule;
u_int16_t rulenum; /* rule or old_set */
u_int8_t cmd, new_set;
@ -2231,6 +2317,9 @@ del_entry(struct ip_fw **chain, u_int32_t arg)
return EINVAL;
}
IPFW_LOCK(chain);
rule = chain->rules;
chain->reap = NULL;
switch (cmd) {
case 0: /* delete rules with given number */
/*
@ -2238,64 +2327,69 @@ del_entry(struct ip_fw **chain, u_int32_t arg)
*/
for (; rule->rulenum < rulenum; prev = rule, rule = rule->next)
;
if (rule->rulenum != rulenum)
if (rule->rulenum != rulenum) {
IPFW_UNLOCK(chain);
return EINVAL;
}
s = splimp(); /* no access to rules while removing */
/*
* flush pointers outside the loop, then delete all matching
* rules. prev remains the same throughout the cycle.
*/
flush_rule_ptrs();
flush_rule_ptrs(chain);
while (rule->rulenum == rulenum)
rule = delete_rule(chain, prev, rule);
splx(s);
rule = remove_rule(chain, rule, prev);
break;
case 1: /* delete all rules with given set number */
s = splimp();
flush_rule_ptrs();
flush_rule_ptrs(chain);
rule = chain->rules;
while (rule->rulenum < IPFW_DEFAULT_RULE)
if (rule->set == rulenum)
rule = delete_rule(chain, prev, rule);
rule = remove_rule(chain, rule, prev);
else {
prev = rule;
rule = rule->next;
}
splx(s);
break;
case 2: /* move rules with given number to new set */
s = splimp();
rule = chain->rules;
for (; rule->rulenum < IPFW_DEFAULT_RULE; rule = rule->next)
if (rule->rulenum == rulenum)
rule->set = new_set;
splx(s);
break;
case 3: /* move rules with given set number to new set */
s = splimp();
for (; rule->rulenum < IPFW_DEFAULT_RULE; rule = rule->next)
if (rule->set == rulenum)
rule->set = new_set;
splx(s);
break;
case 4: /* swap two sets */
s = splimp();
for (; rule->rulenum < IPFW_DEFAULT_RULE; rule = rule->next)
if (rule->set == rulenum)
rule->set = new_set;
else if (rule->set == new_set)
rule->set = rulenum;
splx(s);
break;
}
/*
* Look for rules to reclaim. We grab the list before
* releasing the lock then reclaim them w/o the lock to
* avoid a LOR with dummynet.
*/
rule = chain->reap;
chain->reap = NULL;
IPFW_UNLOCK(chain);
if (rule)
reap_rules(rule);
return 0;
}
/*
* Clear counters for a specific rule.
* The enclosing "table" is assumed locked.
*/
static void
clear_counters(struct ip_fw *rule, int log_only)
@ -2317,18 +2411,16 @@ clear_counters(struct ip_fw *rule, int log_only)
* @arg log_only is 1 if we only want to reset logs, zero otherwise.
*/
static int
zero_entry(int rulenum, int log_only)
zero_entry(struct ip_fw_chain *chain, int rulenum, int log_only)
{
struct ip_fw *rule;
int s;
char *msg;
IPFW_LOCK(chain);
if (rulenum == 0) {
s = splimp();
norule_counter = 0;
for (rule = layer3_chain; rule; rule = rule->next)
for (rule = chain->rules; rule; rule = rule->next)
clear_counters(rule, log_only);
splx(s);
msg = log_only ? "ipfw: All logging counts reset.\n" :
"ipfw: Accounting cleared.\n";
} else {
@ -2337,22 +2429,24 @@ zero_entry(int rulenum, int log_only)
* We can have multiple rules with the same number, so we
* need to clear them all.
*/
for (rule = layer3_chain; rule; rule = rule->next)
for (rule = chain->rules; rule; rule = rule->next)
if (rule->rulenum == rulenum) {
s = splimp();
while (rule && rule->rulenum == rulenum) {
clear_counters(rule, log_only);
rule = rule->next;
}
splx(s);
cleared = 1;
break;
}
if (!cleared) /* we did not find any matching rules */
if (!cleared) { /* we did not find any matching rules */
IPFW_UNLOCK(chain);
return (EINVAL);
}
msg = log_only ? "ipfw: Entry %d logging count reset.\n" :
"ipfw: Entry %d cleared.\n";
}
IPFW_UNLOCK(chain);
if (fw_verbose)
log(LOG_SECURITY | LOG_NOTICE, msg, rulenum);
return (0);
@ -2542,6 +2636,69 @@ bad_size:
return EINVAL;
}
/*
* Copy the static and dynamic rules to the supplied buffer
* and return the amount of space actually used.
*/
static size_t
ipfw_getrules(struct ip_fw_chain *chain, void *buf, size_t space)
{
char *bp = buf;
char *ep = bp + space;
struct ip_fw *rule;
int i;
/* XXX this can take a long time and locking will block packet flow */
IPFW_LOCK(chain);
for (rule = chain->rules; rule ; rule = rule->next) {
/*
* Verify the entry fits in the buffer in case the
* rules changed between calculating buffer space and
* now. This would be better done using a generation
* number but should suffice for now.
*/
i = RULESIZE(rule);
if (bp + i <= ep) {
bcopy(rule, bp, i);
bcopy(&set_disable, &(((struct ip_fw *)bp)->next_rule),
sizeof(set_disable));
bp += i;
}
}
IPFW_UNLOCK(chain);
if (ipfw_dyn_v) {
ipfw_dyn_rule *p, *last = NULL;
IPFW_DYN_LOCK();
for (i = 0 ; i < curr_dyn_buckets; i++)
for (p = ipfw_dyn_v[i] ; p != NULL; p = p->next) {
if (bp + sizeof *p <= ep) {
ipfw_dyn_rule *dst =
(ipfw_dyn_rule *)bp;
bcopy(p, dst, sizeof *p);
bcopy(&(p->rule->rulenum), &(dst->rule),
sizeof(p->rule->rulenum));
/*
* store a non-null value in "next".
* The userland code will interpret a
* NULL here as a marker
* for the last dynamic rule.
*/
bcopy(&dst, &dst->next, sizeof(dst));
last = dst;
dst->expire =
TIME_LEQ(dst->expire, time_second) ?
0 : dst->expire - time_second ;
bp += sizeof(ipfw_dyn_rule);
}
}
IPFW_DYN_UNLOCK();
if (last != NULL) /* mark last dynamic rule */
bzero(&last->next, sizeof(last));
}
return (bp - (char *)buf);
}
/**
* {set|get}sockopt parser.
@ -2549,11 +2706,11 @@ bad_size:
static int
ipfw_ctl(struct sockopt *sopt)
{
int error, s, rulenum;
#define RULE_MAXSIZE (256*sizeof(u_int32_t))
int error, rule_num;
size_t size;
struct ip_fw *bp , *buf, *rule;
static u_int32_t rule_buf[255]; /* we copy the data here */
struct ip_fw *buf, *rule;
u_int32_t rulenum[2];
/*
* Disallow modifications in really-really secure mode, but still allow
@ -2580,8 +2737,12 @@ ipfw_ctl(struct sockopt *sopt)
* come first (the last of which has number IPFW_DEFAULT_RULE),
* followed by a possibly empty list of dynamic rule.
* The last dynamic rule has NULL in the "next" field.
*
* Note that the calculated size is used to bound the
* amount of data returned to the user. The rule set may
* change between calculating the size and returning the
* data in which case we'll just return what fits.
*/
s = splimp();
size = static_len; /* size of static rules */
if (ipfw_dyn_v) /* add size of dyn.rules */
size += (dyn_count * sizeof(ipfw_dyn_rule));
@ -2592,49 +2753,8 @@ ipfw_ctl(struct sockopt *sopt)
* buffer, just jump to the sooptcopyout.
*/
buf = malloc(size, M_TEMP, M_WAITOK);
if (buf == 0) {
splx(s);
error = ENOBUFS;
break;
}
bp = buf;
for (rule = layer3_chain; rule ; rule = rule->next) {
int i = RULESIZE(rule);
bcopy(rule, bp, i);
bcopy(&set_disable, &(bp->next_rule),
sizeof(set_disable));
bp = (struct ip_fw *)((char *)bp + i);
}
if (ipfw_dyn_v) {
int i;
ipfw_dyn_rule *p, *dst, *last = NULL;
dst = (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);
bcopy(&(p->rule->rulenum), &(dst->rule),
sizeof(p->rule->rulenum));
/*
* store a non-null value in "next".
* The userland code will interpret a
* NULL here as a marker
* for the last dynamic rule.
*/
bcopy(&dst, &dst->next, sizeof(dst));
last = dst ;
dst->expire =
TIME_LEQ(dst->expire, time_second) ?
0 : dst->expire - time_second ;
}
if (last != NULL) /* mark last dynamic rule */
bzero(&last->next, sizeof(last));
}
splx(s);
error = sooptcopyout(sopt, buf, size);
error = sooptcopyout(sopt, buf,
ipfw_getrules(&layer3_chain, buf, size));
free(buf, M_TEMP);
break;
@ -2652,23 +2772,28 @@ ipfw_ctl(struct sockopt *sopt)
* the old list without the need for a lock.
*/
s = splimp();
IPFW_LOCK(&layer3_chain);
layer3_chain.reap = NULL;
free_chain(&layer3_chain, 0 /* keep default rule */);
splx(s);
rule = layer3_chain.reap, layer3_chain.reap = NULL;
IPFW_UNLOCK(&layer3_chain);
if (layer3_chain.reap != NULL)
reap_rules(rule);
break;
case IP_FW_ADD:
rule = (struct ip_fw *)rule_buf; /* XXX do a malloc */
error = sooptcopyin(sopt, rule, sizeof(rule_buf),
rule = malloc(RULE_MAXSIZE, M_TEMP, M_WAITOK);
error = sooptcopyin(sopt, rule, RULE_MAXSIZE,
sizeof(struct ip_fw) );
size = sopt->sopt_valsize;
if (error || (error = check_ipfw_struct(rule, size)))
break;
error = add_rule(&layer3_chain, rule);
size = RULESIZE(rule);
if (!error && sopt->sopt_dir == SOPT_GET)
error = sooptcopyout(sopt, rule, size);
if (error == 0)
error = check_ipfw_struct(rule, sopt->sopt_valsize);
if (error == 0) {
error = add_rule(&layer3_chain, rule);
size = RULESIZE(rule);
if (!error && sopt->sopt_dir == SOPT_GET)
error = sooptcopyout(sopt, rule, size);
}
free(rule, M_TEMP);
break;
case IP_FW_DEL:
@ -2684,16 +2809,16 @@ ipfw_ctl(struct sockopt *sopt)
* first u_int32_t contains sets to be disabled,
* second u_int32_t contains sets to be enabled.
*/
error = sooptcopyin(sopt, rule_buf,
error = sooptcopyin(sopt, rulenum,
2*sizeof(u_int32_t), sizeof(u_int32_t));
if (error)
break;
size = sopt->sopt_valsize;
if (size == sizeof(u_int32_t)) /* delete or reassign */
error = del_entry(&layer3_chain, rule_buf[0]);
error = del_entry(&layer3_chain, rulenum[0]);
else if (size == 2*sizeof(u_int32_t)) /* set enable/disable */
set_disable =
(set_disable | rule_buf[0]) & ~rule_buf[1] &
(set_disable | rulenum[0]) & ~rulenum[1] &
~(1<<RESVD_SET); /* set RESVD_SET always enabled */
else
error = EINVAL;
@ -2701,15 +2826,15 @@ ipfw_ctl(struct sockopt *sopt)
case IP_FW_ZERO:
case IP_FW_RESETLOG: /* argument is an int, the rule number */
rulenum=0;
rule_num = 0;
if (sopt->sopt_val != 0) {
error = sooptcopyin(sopt, &rulenum,
error = sooptcopyin(sopt, &rule_num,
sizeof(int), sizeof(int));
if (error)
break;
}
error = zero_entry(rulenum, sopt->sopt_name == IP_FW_RESETLOG);
error = zero_entry(&layer3_chain, rule_num,
sopt->sopt_name == IP_FW_RESETLOG);
break;
default:
@ -2718,6 +2843,7 @@ ipfw_ctl(struct sockopt *sopt)
}
return (error);
#undef RULE_MAXSIZE
}
/**
@ -2737,13 +2863,12 @@ static void
ipfw_tick(void * __unused unused)
{
int i;
int s;
ipfw_dyn_rule *q;
if (dyn_keepalive == 0 || ipfw_dyn_v == NULL || dyn_count == 0)
goto done;
s = splimp();
IPFW_DYN_LOCK();
for (i = 0 ; i < curr_dyn_buckets ; i++) {
for (q = ipfw_dyn_v[i] ; q ; q = q->next ) {
if (q->dyn_type == O_LIMIT_PARENT)
@ -2762,19 +2887,21 @@ ipfw_tick(void * __unused unused)
send_pkt(&(q->id), q->ack_fwd - 1, q->ack_rev, 0);
}
}
splx(s);
IPFW_DYN_UNLOCK();
done:
ipfw_timeout_h = timeout(ipfw_tick, NULL, dyn_keepalive_period*hz);
callout_reset(&ipfw_timeout, dyn_keepalive_period*hz, ipfw_tick, NULL);
}
static void
static int
ipfw_init(void)
{
struct ip_fw default_rule;
int error;
ip_fw_chk_ptr = ipfw_chk;
ip_fw_ctl_ptr = ipfw_ctl;
layer3_chain = NULL;
layer3_chain.rules = NULL;
IPFW_LOCK_INIT(&layer3_chain);
IPFW_DYN_LOCK_INIT();
callout_init(&ipfw_timeout, CALLOUT_MPSAFE);
bzero(&default_rule, sizeof default_rule);
@ -2790,9 +2917,16 @@ ipfw_init(void)
#endif
O_DENY;
add_rule(&layer3_chain, &default_rule);
error = add_rule(&layer3_chain, &default_rule);
if (error != 0) {
printf("ipfw2: error %u initializing default rule "
"(support disabled)\n", error);
IPFW_DYN_LOCK_DESTROY();
IPFW_LOCK_DESTROY(&layer3_chain);
return (error);
}
ip_fw_default_rule = layer3_chain;
ip_fw_default_rule = layer3_chain.rules;
printf("ipfw2 initialized, divert %s, "
"rule-based forwarding enabled, default to %s, logging ",
#ifdef IPDIVERT
@ -2815,42 +2949,53 @@ ipfw_init(void)
else
printf("limited to %d packets/entry by default\n",
verbose_limit);
bzero(&ipfw_timeout_h, sizeof(struct callout_handle));
ipfw_timeout_h = timeout(ipfw_tick, NULL, hz);
ip_fw_chk_ptr = ipfw_chk;
ip_fw_ctl_ptr = ipfw_ctl;
callout_reset(&ipfw_timeout, hz, ipfw_tick, NULL);
return (0);
}
static void
ipfw_destroy(void)
{
struct ip_fw *reap;
IPFW_LOCK(&layer3_chain);
callout_stop(&ipfw_timeout);
ip_fw_chk_ptr = NULL;
ip_fw_ctl_ptr = NULL;
layer3_chain.reap = NULL;
free_chain(&layer3_chain, 1 /* kill default rule */);
reap = layer3_chain.reap, layer3_chain.reap = NULL;
IPFW_UNLOCK(&layer3_chain);
if (reap != NULL)
reap_rules(reap);
IPFW_DYN_LOCK_DESTROY();
IPFW_LOCK_DESTROY(&layer3_chain);
printf("IP firewall unloaded\n");
}
static int
ipfw_modevent(module_t mod, int type, void *unused)
{
int s;
int err = 0;
switch (type) {
case MOD_LOAD:
s = splimp();
if (IPFW_LOADED) {
splx(s);
printf("IP firewall already loaded\n");
err = EEXIST;
} else {
ipfw_init();
splx(s);
err = ipfw_init();
}
break;
case MOD_UNLOAD:
#if !defined(KLD_MODULE)
printf("ipfw statically compiled, cannot unload\n");
err = EBUSY;
#else
s = splimp();
untimeout(ipfw_tick, NULL, ipfw_timeout_h);
ip_fw_chk_ptr = NULL;
ip_fw_ctl_ptr = NULL;
free_chain(&layer3_chain, 1 /* kill default rule */);
splx(s);
printf("IP firewall unloaded\n");
#endif
ipfw_destroy();
err = 0;
break;
default:
break;