Rework ipfw dynamic states implementation to be lockless on fast path.

o added struct ipfw_dyn_info that keeps all needed for ipfw_chk and
  for dynamic states implementation information;
o added DYN_LOOKUP_NEEDED() macro that can be used to determine the
  need of new lookup of dynamic states;
o ipfw_dyn_rule now becomes obsolete. Currently it used to pass
  information from kernel to userland only.
o IPv4 and IPv6 states now described by different structures
  dyn_ipv4_state and dyn_ipv6_state;
o IPv6 scope zones support is added;
o ipfw(4) now depends from Concurrency Kit;
o states are linked with "entry" field using CK_SLIST. This allows
  lockless lookup and protected by mutex modifications.
o the "expired" SLIST field is used for states expiring.
o struct dyn_data is used to keep generic information for both IPv4
  and IPv6;
o struct dyn_parent is used to keep O_LIMIT_PARENT information;
o IPv4 and IPv6 states are stored in different hash tables;
o O_LIMIT_PARENT states now are kept separately from O_LIMIT and
  O_KEEP_STATE states;
o per-cpu dyn_hp pointers are used to implement hazard pointers and they
  prevent freeing states that are locklessly used by lookup threads;
o mutexes to protect modification of lists in hash tables now kept in
  separate arrays. 65535 limit to maximum number of hash buckets now
  removed.
o Separate lookup and install functions added for IPv4 and IPv6 states
  and for parent states.
o By default now is used Jenkinks hash function.

Obtained from:	Yandex LLC
MFC after:	42 days
Sponsored by:	Yandex LLC
Differential Revision:	https://reviews.freebsd.org/D12685
This commit is contained in:
ae 2018-02-07 18:59:54 +00:00
parent df0cd8ad05
commit 545ce709f1
7 changed files with 2606 additions and 1181 deletions

View File

@ -4374,7 +4374,8 @@ netpfil/ipfw/ip_dn_io.c optional inet dummynet
netpfil/ipfw/ip_dn_glue.c optional inet dummynet
netpfil/ipfw/ip_fw2.c optional inet ipfirewall
netpfil/ipfw/ip_fw_bpf.c optional inet ipfirewall
netpfil/ipfw/ip_fw_dynamic.c optional inet ipfirewall
netpfil/ipfw/ip_fw_dynamic.c optional inet ipfirewall \
compile-with "${NORMAL_C} -I$S/contrib/ck/include"
netpfil/ipfw/ip_fw_eaction.c optional inet ipfirewall
netpfil/ipfw/ip_fw_log.c optional inet ipfirewall
netpfil/ipfw/ip_fw_pfil.c optional inet ipfirewall

View File

@ -9,7 +9,7 @@ SRCS+= ip_fw_sockopt.c ip_fw_table.c ip_fw_table_algo.c ip_fw_iface.c
SRCS+= ip_fw_table_value.c
SRCS+= opt_inet.h opt_inet6.h opt_ipdivert.h opt_ipfw.h
CFLAGS+= -DIPFIREWALL
CFLAGS+= -DIPFIREWALL -I${SRCTOP}/sys/contrib/ck/include
#
#If you want it verbose
#CFLAGS+= -DIPFIREWALL_VERBOSE

View File

@ -671,7 +671,7 @@ struct ipfw_flow_id {
uint32_t src_ip;
uint16_t dst_port;
uint16_t src_port;
uint8_t fib;
uint8_t fib; /* XXX: must be uint16_t */
uint8_t proto;
uint8_t _flags; /* protocol-specific flags */
uint8_t addr_type; /* 4=ip4, 6=ip6, 1=ether ? */
@ -682,6 +682,7 @@ struct ipfw_flow_id {
};
#endif
#define IS_IP4_FLOW_ID(id) ((id)->addr_type == 4)
#define IS_IP6_FLOW_ID(id) ((id)->addr_type == 6)
/*

View File

@ -1387,8 +1387,7 @@ ipfw_chk(struct ip_fw_args *args)
* MATCH_NONE when checked and not matched (q = NULL),
* MATCH_FORWARD or MATCH_REVERSE otherwise (q != NULL)
*/
int dyn_dir = MATCH_UNKNOWN;
uint16_t dyn_name = 0;
struct ipfw_dyn_info dyn_info;
struct ip_fw *q = NULL;
struct ip_fw_chain *chain = &V_layer3_chain;
@ -1420,6 +1419,7 @@ ipfw_chk(struct ip_fw_args *args)
proto = args->f_id.proto = 0; /* mark f_id invalid */
/* XXX 0 is a valid proto: IP/IPv6 Hop-by-Hop Option */
DYN_INFO_INIT(&dyn_info);
/*
* PULLUP_TO(len, p, T) makes sure that len + sizeof(T) is contiguous,
* then it sets p to point at the offset "len" in the mbuf. WARNING: the
@ -2605,7 +2605,8 @@ do { \
case O_LIMIT:
case O_KEEP_STATE:
if (ipfw_dyn_install_state(chain, f,
(ipfw_insn_limit *)cmd, args, tablearg)) {
(ipfw_insn_limit *)cmd, args, ulp,
pktlen, &dyn_info, tablearg)) {
/* error or limit violation */
retval = IP_FW_DENY;
l = 0; /* exit inner loop */
@ -2619,34 +2620,15 @@ do { \
/*
* dynamic rules are checked at the first
* keep-state or check-state occurrence,
* with the result being stored in dyn_dir
* and dyn_name.
* with the result being stored in dyn_info.
* The compiler introduces a PROBE_STATE
* instruction for us when we have a
* KEEP_STATE (because PROBE_STATE needs
* to be run first).
*
* (dyn_dir == MATCH_UNKNOWN) means this is
* first lookup for such f_id. Do lookup.
*
* (dyn_dir != MATCH_UNKNOWN &&
* dyn_name != 0 && dyn_name != cmd->arg1)
* means previous lookup didn't find dynamic
* rule for specific state name and current
* lookup will search rule with another state
* name. Redo lookup.
*
* (dyn_dir != MATCH_UNKNOWN && dyn_name == 0)
* means previous lookup was for `any' name
* and it didn't find rule. No need to do
* lookup again.
*/
if ((dyn_dir == MATCH_UNKNOWN ||
(dyn_name != 0 &&
dyn_name != cmd->arg1)) &&
(q = ipfw_dyn_lookup_state(&args->f_id,
ulp, pktlen, &dyn_dir,
(dyn_name = cmd->arg1))) != NULL) {
if (DYN_LOOKUP_NEEDED(&dyn_info, cmd) &&
(q = ipfw_dyn_lookup_state(args, ulp,
pktlen, cmd, &dyn_info)) != NULL) {
/*
* Found dynamic entry, jump to the
* 'action' part of the parent rule
@ -2654,13 +2636,7 @@ do { \
* cmdlen.
*/
f = q;
/* XXX we would like to have f_pos
* readily accessible in the dynamic
* rule, instead of having to
* lookup q->rule.
*/
f_pos = ipfw_find_rule(chain,
f->rulenum, f->id);
f_pos = dyn_info.f_pos;
cmd = ACTION_PTR(f);
l = f->cmd_len - f->act_ofs;
cmdlen = 0;
@ -2877,7 +2853,8 @@ do { \
case O_FORWARD_IP:
if (args->eh) /* not valid on layer2 pkts */
break;
if (q != f || dyn_dir == MATCH_FORWARD) {
if (q != f ||
dyn_info.direction == MATCH_FORWARD) {
struct sockaddr_in *sa;
sa = &(((ipfw_insn_sa *)cmd)->sa);
@ -2937,7 +2914,8 @@ do { \
case O_FORWARD_IP6:
if (args->eh) /* not valid on layer2 pkts */
break;
if (q != f || dyn_dir == MATCH_FORWARD) {
if (q != f ||
dyn_info.direction == MATCH_FORWARD) {
struct sockaddr_in6 *sin6;
sin6 = &(((ipfw_insn_sa6 *)cmd)->sa);
@ -3089,7 +3067,7 @@ do { \
* @args content, and it may be
* used for new state lookup later.
*/
dyn_dir = MATCH_UNKNOWN;
DYN_INFO_INIT(&dyn_info);
}
break;

File diff suppressed because it is too large Load Diff

View File

@ -184,23 +184,48 @@ enum { /* result for matching dynamic rules */
struct ip_fw_chain;
struct sockopt_data;
int ipfw_is_dyn_rule(struct ip_fw *rule);
void ipfw_expire_dyn_rules(struct ip_fw_chain *, ipfw_range_tlv *);
void ipfw_expire_dyn_states(struct ip_fw_chain *, ipfw_range_tlv *);
struct tcphdr;
struct mbuf *ipfw_send_pkt(struct mbuf *, struct ipfw_flow_id *,
u_int32_t, u_int32_t, int);
/*
* Macro to determine that we need to do or redo dynamic state lookup.
* direction == MATCH_UNKNOWN means that this is first lookup, then we need
* to do lookup.
* Otherwise check the state name, if previous lookup was for "any" name,
* this means there is no state with specific name. Thus no need to do
* lookup. If previous name was not "any", redo lookup for specific name.
*/
#define DYN_LOOKUP_NEEDED(p, cmd) \
((p)->direction == MATCH_UNKNOWN || \
((p)->kidx != 0 && (p)->kidx != (cmd)->arg1))
#define DYN_INFO_INIT(p) do { \
(p)->direction = MATCH_UNKNOWN; \
(p)->kidx = 0; \
} while (0)
struct ipfw_dyn_info {
uint16_t direction; /* match direction */
uint16_t kidx; /* state name kidx */
uint32_t hashval; /* hash value */
uint32_t version; /* bucket version */
uint32_t f_pos;
};
int ipfw_dyn_install_state(struct ip_fw_chain *chain, struct ip_fw *rule,
ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg);
struct ip_fw *ipfw_dyn_lookup_state(const struct ipfw_flow_id *pkt,
const void *ulp, int pktlen, int *match_direction, uint16_t kidx);
void ipfw_remove_dyn_children(struct ip_fw *rule);
const ipfw_insn_limit *cmd, const struct ip_fw_args *args,
const void *ulp, int pktlen, struct ipfw_dyn_info *info,
uint32_t tablearg);
struct ip_fw *ipfw_dyn_lookup_state(const struct ip_fw_args *args,
const void *ulp, int pktlen, const ipfw_insn *cmd,
struct ipfw_dyn_info *info);
void ipfw_get_dynamic(struct ip_fw_chain *chain, char **bp, const char *ep);
int ipfw_dump_states(struct ip_fw_chain *chain, struct sockopt_data *sd);
void ipfw_dyn_init(struct ip_fw_chain *); /* per-vnet initialization */
void ipfw_dyn_uninit(int); /* per-vnet deinitialization */
int ipfw_dyn_len(void);
int ipfw_dyn_get_count(void);
uint32_t ipfw_dyn_get_count(void);
/* common variables */
VNET_DECLARE(int, fw_one_pass);

View File

@ -1062,7 +1062,7 @@ delete_range(struct ip_fw_chain *chain, ipfw_range_tlv *rt, int *ndel)
map = swap_map(chain, map, chain->n_rules - n);
/* 6. Remove all dynamic states originated by deleted rules */
if (ndyn > 0)
ipfw_expire_dyn_rules(chain, rt);
ipfw_expire_dyn_states(chain, rt);
/* 7. now remove the rules deleted from the old map */
for (i = start; i < end; i++) {
rule = map[i];