summaryrefslogtreecommitdiffstats
path: root/sys/netinet/ip_fw2.c
diff options
context:
space:
mode:
authorsam <sam@FreeBSD.org>2003-09-17 00:56:50 +0000
committersam <sam@FreeBSD.org>2003-09-17 00:56:50 +0000
commit979dec8477e95454857abea521877696964f051a (patch)
tree2ae56f09d130fb23cfd3b7ea286e52ee7cea423e /sys/netinet/ip_fw2.c
parentd5676276aa2be8b4a449fe3170baf97654371f86 (diff)
downloadFreeBSD-src-979dec8477e95454857abea521877696964f051a.zip
FreeBSD-src-979dec8477e95454857abea521877696964f051a.tar.gz
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
Diffstat (limited to 'sys/netinet/ip_fw2.c')
-rw-r--r--sys/netinet/ip_fw2.c473
1 files changed, 309 insertions, 164 deletions
diff --git a/sys/netinet/ip_fw2.c b/sys/netinet/ip_fw2.c
index b26d3a6..cf314be 100644
--- a/sys/netinet/ip_fw2.c
+++ b/sys/netinet/ip_fw2.c
@@ -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;
}
+/**
+ * Reclaim storage associated with a list of rules. This is
+ * typically the list created using remove_rule.
+ */
+static void
+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);
+ }
+}
+
/*
- * Deletes all rules from a chain (except rules in set RESVD_SET
- * unless kill_default = 1).
- * Must be called at splimp().
+ * 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, int kill_default)
+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;
OpenPOWER on IntegriCloud