diff options
Diffstat (limited to 'sys/netinet/ip_fw2.c')
-rw-r--r-- | sys/netinet/ip_fw2.c | 163 |
1 files changed, 116 insertions, 47 deletions
diff --git a/sys/netinet/ip_fw2.c b/sys/netinet/ip_fw2.c index 5f86a7c..f5753e6 100644 --- a/sys/netinet/ip_fw2.c +++ b/sys/netinet/ip_fw2.c @@ -78,6 +78,14 @@ #include <machine/in_cksum.h> /* XXX for in_cksum */ /* + * XXX This one should go in sys/mbuf.h. It is used to avoid that + * a firewall-generated packet loops forever through the firewall. + */ +#ifndef M_SKIP_FIREWALL +#define M_SKIP_FIREWALL 0x4000 +#endif + +/* * set_disable contains one bit per set value (0..31). * If the bit is set, all rules with the corresponding set * are disabled. Set 31 is reserved for the default rule @@ -168,12 +176,22 @@ static u_int32_t dyn_rst_lifetime = 1; static u_int32_t dyn_udp_lifetime = 10; static u_int32_t dyn_short_lifetime = 5; +/* + * Keepalives are sent if dyn_keepalive is set. They are sent every + * dyn_keepalive_period seconds, in the last dyn_keepalive_interval + * seconds of lifetime of a rule. + * dyn_rst_lifetime and dyn_fin_lifetime should be strictly lower + * than dyn_keepalive_period. + */ + +static u_int32_t dyn_keepalive_interval = 20; +static u_int32_t dyn_keepalive_period = 5; static u_int32_t dyn_keepalive = 1; /* do send keepalives */ static u_int32_t static_count; /* # of static rules */ static u_int32_t static_len; /* size in bytes of static rules */ static u_int32_t dyn_count; /* # of dynamic rules */ -static u_int32_t dyn_max = 1000; /* max # of dynamic rules */ +static u_int32_t dyn_max = 4096; /* max # of dynamic rules */ SYSCTL_INT(_net_inet_ip_fw, OID_AUTO, dyn_buckets, CTLFLAG_RW, &dyn_buckets, 0, "Number of dyn. buckets"); @@ -754,6 +772,7 @@ next: case TH_SYN: /* opening */ q->expire = time_second + dyn_syn_lifetime; break; + case BOTH_SYN: /* move to established */ case BOTH_SYN | TH_FIN : /* one side tries to close */ case BOTH_SYN | (TH_FIN << 8) : @@ -776,9 +795,13 @@ next: } q->expire = time_second + dyn_ack_lifetime; break; + case BOTH_SYN | BOTH_FIN: /* both sides closed */ + if (dyn_fin_lifetime >= dyn_keepalive_period) + dyn_fin_lifetime = dyn_keepalive_period - 1; q->expire = time_second + dyn_fin_lifetime; break; + default: #if 0 /* @@ -788,6 +811,8 @@ next: if ( (q->state & ((TH_RST << 8)|TH_RST)) == 0) printf("invalid state: 0x%x\n", q->state); #endif + if (dyn_rst_lifetime >= dyn_keepalive_period) + dyn_rst_lifetime = dyn_keepalive_period - 1; q->expire = time_second + dyn_rst_lifetime; break; } @@ -806,8 +831,14 @@ done: static void realloc_dynamic_table(void) { - /* try reallocation, make sure we have a power of 2 */ + /* + * Try reallocation, make sure we have a power of 2 and do + * not allow more than 64k entries. In case of overflow, + * default to 1024. + */ + if (dyn_buckets > 65536) + dyn_buckets = 1024; if ((dyn_buckets & (dyn_buckets-1)) != 0) { /* not a power of 2 */ dyn_buckets = curr_dyn_buckets; /* reset */ return; @@ -815,8 +846,13 @@ realloc_dynamic_table(void) curr_dyn_buckets = dyn_buckets; if (ipfw_dyn_v != NULL) free(ipfw_dyn_v, M_IPFW); - ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof(ipfw_dyn_rule *), + for (;;) { + ipfw_dyn_v = malloc(curr_dyn_buckets * sizeof(ipfw_dyn_rule *), M_IPFW, M_DONTWAIT | M_ZERO); + if (ipfw_dyn_v != NULL || curr_dyn_buckets <= 2) + break; + curr_dyn_buckets /= 2; + } } /** @@ -1084,6 +1120,7 @@ send_pkt(struct ipfw_flow_id *id, u_int32_t seq, u_int32_t ack, int flags) ip->ip_len = m->m_pkthdr.len; bzero (&sro, sizeof (sro)); ip_rtaddr(ip->ip_dst, &sro); + m->m_flags |= M_SKIP_FIREWALL; ip_output(m, NULL, &sro, 0, NULL); if (sro.ro_rt) RTFREE(sro.ro_rt); @@ -1191,8 +1228,7 @@ ipfw_chk(struct ip_fw_args *args) * are documented here. Should you change them, please check * the implementation of the various instructions to make sure * that they still work. - */ - /* + * * args->eh The MAC header. It is non-null for a layer2 * packet, it is NULL for a layer-3 packet. * @@ -1254,6 +1290,8 @@ ipfw_chk(struct ip_fw_args *args) int dyn_dir = MATCH_UNKNOWN; ipfw_dyn_rule *q = NULL; + if (m->m_flags & M_SKIP_FIREWALL) + return 0; /* accept */ /* * dyn_dir = MATCH_UNKNOWN when rules unchecked, * MATCH_NONE when checked and not matched (q = NULL), @@ -2057,39 +2095,41 @@ free_chain(struct ip_fw **chain, int kill_default) /** * Remove all rules with given number, and also do set manipulation. * - * The argument is an int. The low 16 bit are the - * rule or set number, the upper 16 bits are the - * function, namely: + * The argument is an u_int32_t. The low 16 bit are the rule or set number, + * the next 8 bits are the new set, the top 8 bits are the command: * - * 0 DEL_SINGLE_RULE - * 1 DELETE_RULESET - * 2 DISABLE_SET - * 3 ENABLE_SET + * 0 delete rules with given number + * 1 delete rules with given set number + * 2 move rules with given number to new set + * 3 move rules with given set number to new set + * 4 swap sets with given numbers */ static int del_entry(struct ip_fw **chain, u_int32_t arg) { struct ip_fw *prev, *rule; int s; - - u_int16_t rulenum, cmd; + u_int16_t rulenum; + u_int8_t cmd, new_set; rulenum = arg & 0xffff; - cmd = (arg >> 16) & 0xffff; + cmd = (arg >> 24) & 0xff; + new_set = (arg >> 16) & 0xff; - if (cmd > 3) - return EINVAL; - if (cmd == 0 && rulenum == IPFW_DEFAULT_RULE) + if (cmd > 4) return EINVAL; - - if (cmd != 0 && rulenum > 30) { - printf("ipfw: del_entry: invalid set number %d\n", - rulenum); + if (new_set > 30) return EINVAL; + if (cmd == 0 || cmd == 2) { + if (rulenum == IPFW_DEFAULT_RULE) + return EINVAL; + } else { + if (rulenum > 30) + return EINVAL; } - + switch (cmd) { - case 0: /* DEL_SINGLE_RULE */ + case 0: /* delete rules with given number */ /* * locate first rule to delete */ @@ -2101,18 +2141,19 @@ del_entry(struct ip_fw **chain, u_int32_t arg) return EINVAL; s = splimp(); /* no access to rules while removing */ - flush_rule_ptrs(); /* more efficient to do outside the loop */ /* - * prev remains the same throughout the cycle + * flush pointers outside the loop, then delete all matching + * rules. prev remains the same throughout the cycle. */ + flush_rule_ptrs(); while (rule && rule->rulenum == rulenum) rule = delete_rule(chain, prev, rule); splx(s); break; - case 1: /* DELETE_RULESET */ - s = splimp(); /* no access to rules while removing */ - flush_rule_ptrs(); /* more efficient to do outside the loop */ + case 1: /* delete all rules with given set number */ + s = splimp(); + flush_rule_ptrs(); for (prev = NULL, rule = *chain; rule ; ) if (rule->set == rulenum) rule = delete_rule(chain, prev, rule); @@ -2123,15 +2164,29 @@ del_entry(struct ip_fw **chain, u_int32_t arg) splx(s); break; - case 2: /* DISABLE SET */ + case 2: /* move rules with given number to new set */ + s = splimp(); + for (rule = *chain; 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(); - set_disable |= 1 << rulenum; + for (rule = *chain; rule ; rule = rule->next) + if (rule->set == rulenum) + rule->set = new_set; splx(s); break; - case 3: /* ENABLE SET */ + case 4: /* swap two sets */ s = splimp(); - set_disable &= ~(1 << rulenum); + for (rule = *chain; rule ; rule = rule->next) + if (rule->set == rulenum) + rule->set = new_set; + else if (rule->set == new_set) + rule->set = rulenum; splx(s); break; } @@ -2515,23 +2570,32 @@ ipfw_ctl(struct sockopt *sopt) error = sooptcopyout(sopt, rule, size); break; - case IP_FW_DEL: /* argument is an int, the rule number */ + case IP_FW_DEL: /* - * IP_FW_DEL is used for deleting single rules, - * set of rules, and manipulating set_disable. - * - * Everything is managed in del_entry(); + * IP_FW_DEL is used for deleting single rules or sets, + * and (ab)used to atomically manipulate sets. Argument size + * is used to distinguish between the two: + * sizeof(u_int32_t) + * delete single rule or set of rules, + * or reassign rules (or sets) to a different set. + * 2*sizeof(u_int32_t) + * atomic disable/enable sets. + * first u_int32_t contains sets to be disabled, + * second u_int32_t contains sets to be enabled. */ - error = sooptcopyin(sopt, &rulenum, sizeof(int), sizeof(int)); + error = sooptcopyin(sopt, rule_buf, + 2*sizeof(u_int32_t), sizeof(u_int32_t)); if (error) break; - if (rulenum == IPFW_DEFAULT_RULE) { - if (fw_debug) - printf("ipfw: can't delete rule %u\n", - (unsigned)IPFW_DEFAULT_RULE); + size = sopt->sopt_valsize; + if (size == sizeof(u_int32_t)) /* delete or reassign */ + error = del_entry(&layer3_chain, rule_buf[0]); + else if (size == 2*sizeof(u_int32_t)) /* set enable/disable */ + set_disable = + (set_disable | rule_buf[0]) & ~rule_buf[1] & + ~(1<<31); /* set 31 always enabled */ + else error = EINVAL; - } else - error = del_entry(&layer3_chain, rulenum); break; case IP_FW_ZERO: @@ -2564,6 +2628,10 @@ ipfw_ctl(struct sockopt *sopt) */ struct ip_fw *ip_fw_default_rule; +/* + * This procedure is only used to handle keepalives. It is invoked + * every dyn_keepalive_period + */ static void ipfw_tick(void * __unused unused) { @@ -2583,7 +2651,8 @@ ipfw_tick(void * __unused unused) continue; if ( (q->state & BOTH_SYN) != BOTH_SYN) continue; - if (TIME_LEQ( time_second+20, q->expire)) + if (TIME_LEQ( time_second+dyn_keepalive_interval, + q->expire)) continue; /* too early */ if (TIME_LEQ(q->expire, time_second)) continue; /* too late, rule expired */ @@ -2594,7 +2663,7 @@ ipfw_tick(void * __unused unused) } splx(s); done: - ipfw_timeout_h = timeout(ipfw_tick, NULL, 5*hz); + ipfw_timeout_h = timeout(ipfw_tick, NULL, dyn_keepalive_period*hz); } static void |