diff --git a/sbin/ipfw/ipfw2.c b/sbin/ipfw/ipfw2.c index a9d9840..5afb28a 100644 --- a/sbin/ipfw/ipfw2.c +++ b/sbin/ipfw/ipfw2.c @@ -1472,12 +1472,17 @@ show_static_rule(struct cmdline_opts *co, struct format_opts *fo, for (l = rule->cmd_len - rule->act_ofs, cmd = ACTION_PTR(rule); l > 0 ; l -= F_LEN(cmd), cmd += F_LEN(cmd)) { switch(cmd->opcode) { - case O_CHECK_STATE: - bprintf(bp, "check-state"); + case O_CHECK_STATE: { + char *ename; + + ename = object_search_ctlv(fo->tstate, cmd->arg1, + IPFW_TLV_STATE_NAME); + bprintf(bp, "check-state %s", ename ? ename: "all"); /* avoid printing anything else */ flags = HAVE_PROTO | HAVE_SRCIP | HAVE_DSTIP | HAVE_IP; break; + } case O_ACCEPT: bprintf(bp, "allow"); @@ -2041,9 +2046,15 @@ show_static_rule(struct cmdline_opts *co, struct format_opts *fo, comment = (char *)(cmd + 1); break; - case O_KEEP_STATE: - bprintf(bp, " keep-state"); + case O_KEEP_STATE: { + char *ename; + + ename = object_search_ctlv(fo->tstate, + cmd->arg1, IPFW_TLV_STATE_NAME); + bprintf(bp, " keep-state %s", + ename ? ename: "all"); break; + } case O_LIMIT: { struct _s_x *p = limit_masks; @@ -3636,6 +3647,16 @@ compile_rule(char *av[], uint32_t *rbuf, int *rbufsize, struct tidx *tstate) case TOK_CHECKSTATE: have_state = action; action->opcode = O_CHECK_STATE; + if (*av != NULL && table_check_name(*av) == 0) { + action->arg1 = pack_object(tstate, *av, + IPFW_TLV_STATE_NAME); + if (action->arg1 == 0) + errx(EX_DATAERR, "state name %s", *av); + av++; + } else { + if (*av != NULL) /* all */ + av++; + } break; case TOK_ACCEPT: @@ -4441,16 +4462,30 @@ read_options: av++; break; - case TOK_KEEPSTATE: + case TOK_KEEPSTATE: { + uint16_t nidx; + if (open_par) errx(EX_USAGE, "keep-state cannot be part " "of an or block"); if (have_state) errx(EX_USAGE, "only one of keep-state " "and limit is allowed"); + if (*av != NULL && table_check_name(*av) == 0) { + nidx = pack_object(tstate, *av, + IPFW_TLV_STATE_NAME); + if (nidx == 0) + errx(EX_DATAERR, "state name %s", *av); + av++; + } else { + if (*av != NULL) /* all */ + av++; + nidx = 0; + } have_state = cmd; - fill_cmd(cmd, O_KEEP_STATE, 0, 0); + fill_cmd(cmd, O_KEEP_STATE, 0, nidx); break; + } case TOK_LIMIT: { ipfw_insn_limit *c = (ipfw_insn_limit *)cmd; @@ -4688,7 +4723,7 @@ done: * generate O_PROBE_STATE if necessary */ if (have_state && have_state->opcode != O_CHECK_STATE) { - fill_cmd(dst, O_PROBE_STATE, 0, 0); + fill_cmd(dst, O_PROBE_STATE, 0, have_state->arg1); dst = next_cmd(dst, &rblen); } diff --git a/sys/netinet/ip_fw.h b/sys/netinet/ip_fw.h index e7d8fcf..21ea923 100644 --- a/sys/netinet/ip_fw.h +++ b/sys/netinet/ip_fw.h @@ -804,6 +804,7 @@ typedef struct _ipfw_obj_tlv { #define IPFW_TLV_NAT64_LSN_NAME 11 #define IPFW_TLV_NAT64_PG_ENT 12 #define IPFW_TLV_COUNTERS 13 +#define IPFW_TLV_STATE_NAME 14 /* Object name TLV */ typedef struct _ipfw_obj_ntlv { diff --git a/sys/netpfil/ipfw/ip_fw2.c b/sys/netpfil/ipfw/ip_fw2.c index 12413b3..c8e1c45 100644 --- a/sys/netpfil/ipfw/ip_fw2.c +++ b/sys/netpfil/ipfw/ip_fw2.c @@ -2133,7 +2133,7 @@ do { \ if (dyn_dir == MATCH_UNKNOWN && (q = ipfw_lookup_dyn_rule(&args->f_id, &dyn_dir, proto == IPPROTO_TCP ? - TCP(ulp): NULL)) + TCP(ulp): NULL, cmd->arg1)) != NULL) { /* * Found dynamic entry, update stats diff --git a/sys/netpfil/ipfw/ip_fw_dynamic.c b/sys/netpfil/ipfw/ip_fw_dynamic.c index b1f430a..852c2db 100644 --- a/sys/netpfil/ipfw/ip_fw_dynamic.c +++ b/sys/netpfil/ipfw/ip_fw_dynamic.c @@ -296,6 +296,129 @@ hash_packet(struct ipfw_flow_id *id, int buckets) return i; } +struct dyn_state_obj { + struct named_object no; + char name[64]; +}; + +static int +dyn_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype) +{ + + if (cmd->arg1 != 0) { + *puidx = cmd->arg1; + return (0); + } + return (1); +} + +static void +dyn_update(ipfw_insn *cmd, uint16_t idx) +{ + + cmd->arg1 = idx; +} + +static int +dyn_findbyname(struct ip_fw_chain *ch, struct tid_info *ti, + struct named_object **pno) +{ + + return (ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti, + IPFW_TLV_STATE_NAME, pno)); +} + +static struct named_object * +dyn_findbykidx(struct ip_fw_chain *ch, uint16_t idx) +{ + + return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx)); +} + +static int +dyn_create(struct ip_fw_chain *ch, struct tid_info *ti, + uint16_t *pkidx) +{ + struct namedobj_instance *ni; + struct dyn_state_obj *obj; + struct named_object *no; + ipfw_obj_ntlv *ntlv; + + if (ti->tlvs == NULL) + return (EINVAL); + ntlv = find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx, + IPFW_TLV_STATE_NAME); + if (ntlv == NULL) + return (EINVAL); + + ni = CHAIN_TO_SRV(ch); + obj = malloc(sizeof(*obj), M_IPFW, M_WAITOK | M_ZERO); + obj->no.name = obj->name; + obj->no.etlv = IPFW_TLV_STATE_NAME; + strlcpy(obj->name, ntlv->name, sizeof(obj->name)); + + IPFW_UH_WLOCK(ch); + no = ipfw_objhash_lookup_name(ni, ti->set, ntlv->name); + if (no != NULL) { + /* Another thread already has allocated this idx */ + *pkidx = no->kidx; + IPFW_UH_WUNLOCK(ch); + free(obj, M_IPFW); + return (0); + } + if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) { + IPFW_UH_WUNLOCK(ch); + free(obj, M_IPFW); + return (ENOSPC); + } + ipfw_objhash_add(ni, &obj->no); + IPFW_WLOCK(ch); + ch->srvstate[obj->no.kidx] = obj; + IPFW_WUNLOCK(ch); + IPFW_UH_WUNLOCK(ch); + return (0); +} + +static void +dyn_destroy(struct ip_fw_chain *ch, struct named_object *no) +{ + struct dyn_state_obj *obj; + + IPFW_UH_WLOCK_ASSERT(ch); + + IPFW_WLOCK(ch); + obj = ch->srvstate[no->kidx]; + ch->srvstate[no->kidx] = NULL; + IPFW_WUNLOCK(ch); + ipfw_objhash_del(CHAIN_TO_SRV(ch), no); + ipfw_objhash_free_idx(CHAIN_TO_SRV(ch), no->kidx); + + free(obj, M_IPFW); +} + +static struct opcode_obj_rewrite dyn_opcodes[] = { + { + O_KEEP_STATE, IPFW_TLV_STATE_NAME, + dyn_classify, dyn_update, + dyn_findbyname, dyn_findbykidx, + dyn_create, dyn_destroy + }, + { + O_CHECK_STATE, IPFW_TLV_STATE_NAME, + dyn_classify, dyn_update, + dyn_findbyname, dyn_findbykidx, + dyn_create, dyn_destroy + }, + { + O_PROBE_STATE, IPFW_TLV_STATE_NAME, + dyn_classify, dyn_update, + dyn_findbyname, dyn_findbykidx, + dyn_create, dyn_destroy + }, +}; +#define DYN_STATE_NAME(ch, cmd) \ + (((struct dyn_state_obj *)((ch)->srvstate[(cmd)->arg1]))->name) + /** * Print customizable flow id description via log(9) facility. */ @@ -338,7 +461,7 @@ print_dyn_rule_flags(struct ipfw_flow_id *id, int dyn_type, int log_flags, */ static ipfw_dyn_rule * lookup_dyn_rule_locked(struct ipfw_flow_id *pkt, int i, int *match_direction, - struct tcphdr *tcp) + struct tcphdr *tcp, uint16_t kidx) { /* * Stateful ipfw extensions. @@ -356,6 +479,9 @@ lookup_dyn_rule_locked(struct ipfw_flow_id *pkt, int i, int *match_direction, if (pkt->proto != q->id.proto || q->dyn_type == O_LIMIT_PARENT) continue; + if (q->rule->cmd->arg1 != kidx) + continue; + if (IS_IP6_FLOW_ID(pkt)) { if (IN6_ARE_ADDR_EQUAL(&pkt->src_ip6, &q->id.src_ip6) && IN6_ARE_ADDR_EQUAL(&pkt->dst_ip6, &q->id.dst_ip6) && @@ -487,7 +613,7 @@ done: ipfw_dyn_rule * ipfw_lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, - struct tcphdr *tcp) + struct tcphdr *tcp, uint16_t kidx) { ipfw_dyn_rule *q; int i; @@ -495,7 +621,7 @@ ipfw_lookup_dyn_rule(struct ipfw_flow_id *pkt, int *match_direction, i = hash_packet(pkt, V_curr_dyn_buckets); IPFW_BUCK_LOCK(i); - q = lookup_dyn_rule_locked(pkt, i, match_direction, tcp); + q = lookup_dyn_rule_locked(pkt, i, match_direction, tcp, kidx); if (q == NULL) IPFW_BUCK_UNLOCK(i); /* NB: return table locked when q is not NULL */ @@ -607,8 +733,8 @@ resize_dynamic_table(struct ip_fw_chain *chain, int nbuckets) * - "parent" rules for the above (O_LIMIT_PARENT). */ static ipfw_dyn_rule * -add_dyn_rule(struct ipfw_flow_id *id, int i, u_int8_t dyn_type, struct ip_fw *rule, - uint16_t match_mask) +add_dyn_rule(struct ipfw_flow_id *id, int i, u_int8_t dyn_type, + struct ip_fw *rule, uint16_t match_mask, uint16_t kidx) { ipfw_dyn_rule *r; @@ -691,7 +817,7 @@ lookup_dyn_parent(struct ipfw_flow_id *pkt, int *pindex, struct ip_fw *rule) } /* Add virtual limiting rule */ - return add_dyn_rule(pkt, i, O_LIMIT_PARENT, rule, 0); + return add_dyn_rule(pkt, i, O_LIMIT_PARENT, rule, 0, 0); } static struct ip_fw ipfwsyncrule = { @@ -724,7 +850,7 @@ ipfwsyncin(struct ipfw_flow_id *f_id) i = hash_packet(f_id, V_curr_dyn_buckets); IPFW_BUCK_LOCK(i); - q = lookup_dyn_rule_locked(f_id, i, NULL, NULL); + q = lookup_dyn_rule_locked(f_id, i, NULL, NULL, 0); if (q != NULL) { q->synced = time_uptime; IPFW_BUCK_UNLOCK(i); @@ -733,7 +859,7 @@ ipfwsyncin(struct ipfw_flow_id *f_id) } q = add_dyn_rule(f_id, i, O_KEEP_STATE, &ipfwsyncrule, - (uint16_t)V_dyn_mask); + (uint16_t)V_dyn_mask, 0); if (q == NULL) printf("syncin rule not found?"); @@ -762,13 +888,14 @@ ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, int i; int match_mask; - DEB(print_dyn_rule(&args->f_id, cmd->o.opcode, "install_state", "");) + DEB(print_dyn_rule(&args->f_id, cmd->o.opcode, "install_state", + (cmd->o.arg1 == 0 ? "": DYN_STATE_NAME(chain, &cmd->o)));) i = hash_packet(&args->f_id, V_curr_dyn_buckets); IPFW_BUCK_LOCK(i); - q = lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL); + q = lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL, cmd->o.arg1); if (q != NULL) { /* should never occur */ DEB( @@ -792,9 +919,9 @@ ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, case O_KEEP_STATE: /* bidir rule */ match_mask = tablearg ? tablearg : V_dyn_mask; q = add_dyn_rule(&args->f_id, i, O_KEEP_STATE, rule, - (uint16_t)match_mask); + (uint16_t)match_mask, cmd->o.arg1); if (q != NULL) - (*ipfwsyncout_p)(&args->f_id); + (*ipfwsyncout_p)(&args->f_id); /* XXX */ break; case O_LIMIT: { /* limit number of sessions */ @@ -870,7 +997,7 @@ ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, IPFW_BUCK_LOCK(i); q = add_dyn_rule(&args->f_id, i, O_LIMIT, - (struct ip_fw *)parent, (uint16_t)V_dyn_mask); + (struct ip_fw *)parent, (uint16_t)V_dyn_mask, cmd->o.arg1); if (q == NULL) { /* Decrement index and notify caller */ IPFW_BUCK_UNLOCK(i); @@ -892,7 +1019,7 @@ ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, } /* XXX just set lifetime */ - lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL); + lookup_dyn_rule_locked(&args->f_id, i, NULL, NULL, cmd->o.arg1); IPFW_BUCK_UNLOCK(i); return (0); @@ -1491,6 +1618,7 @@ ipfw_dyn_init(struct ip_fw_chain *chain) * being added to chain. */ resize_dynamic_table(chain, V_curr_dyn_buckets); + IPFW_ADD_OBJ_REWRITER(IS_DEFAULT_VNET(curvnet), dyn_opcodes); } void @@ -1498,6 +1626,7 @@ ipfw_dyn_uninit(int pass) { int i; + IPFW_DEL_OBJ_REWRITER(IS_DEFAULT_VNET(curvnet), dyn_opcodes); if (pass == 0) { callout_drain(&V_ipfw_timeout); return; diff --git a/sys/netpfil/ipfw/ip_fw_private.h b/sys/netpfil/ipfw/ip_fw_private.h index 2f61fe0..c07c849 100644 --- a/sys/netpfil/ipfw/ip_fw_private.h +++ b/sys/netpfil/ipfw/ip_fw_private.h @@ -189,7 +189,7 @@ struct mbuf *ipfw_send_pkt(struct mbuf *, struct ipfw_flow_id *, int ipfw_install_state(struct ip_fw_chain *chain, struct ip_fw *rule, ipfw_insn_limit *cmd, struct ip_fw_args *args, uint32_t tablearg); ipfw_dyn_rule *ipfw_lookup_dyn_rule(struct ipfw_flow_id *pkt, - int *match_direction, struct tcphdr *tcp); + int *match_direction, struct tcphdr *tcp, uint16_t kidx); void ipfw_remove_dyn_children(struct ip_fw *rule); 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); @@ -563,6 +563,9 @@ typedef struct named_object *(ipfw_obj_fidx_cb)(struct ip_fw_chain *ch, typedef int (ipfw_obj_create_cb)(struct ip_fw_chain *ch, struct tid_info *ti, uint16_t *pkidx); +typedef void (ipfw_obj_destroy_cb)(struct ip_fw_chain *ch, + struct named_object *no); + struct opcode_obj_rewrite { uint32_t opcode; /* Opcode to act upon */ @@ -572,6 +575,7 @@ struct opcode_obj_rewrite { ipfw_obj_fname_cb *find_byname; /* Find named object by name */ ipfw_obj_fidx_cb *find_bykidx; /* Find named object by kidx */ ipfw_obj_create_cb *create_object; /* Create named object */ + ipfw_obj_destroy_cb *destroy_object;/* Destroy named object */ }; #define IPFW_ADD_OBJ_REWRITER(f, c) do { \ @@ -608,6 +612,9 @@ void ipfw_init_counters(void); void ipfw_destroy_counters(void); struct ip_fw *ipfw_alloc_rule(struct ip_fw_chain *chain, size_t rulesize); int ipfw_match_range(struct ip_fw *rule, ipfw_range_tlv *rt); +ipfw_obj_ntlv *find_name_tlv_type(void *tlvs, int len, uint16_t uidx, + uint32_t etlv); + typedef int (sopt_handler_f)(struct ip_fw_chain *ch, ip_fw3_opheader *op3, struct sockopt_data *sd); diff --git a/sys/netpfil/ipfw/ip_fw_sockopt.c b/sys/netpfil/ipfw/ip_fw_sockopt.c index bc574b1..97403de 100644 --- a/sys/netpfil/ipfw/ip_fw_sockopt.c +++ b/sys/netpfil/ipfw/ip_fw_sockopt.c @@ -1466,6 +1466,12 @@ check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) switch (cmd->opcode) { case O_PROBE_STATE: case O_KEEP_STATE: + case O_CHECK_STATE: + if (cmdlen != F_INSN_SIZE(ipfw_insn)) + goto bad_size; + if (cmd->arg1 != 0) + ci->object_opcodes++; + break; case O_PROTO: case O_IP_SRC_ME: case O_IP_DST_ME: @@ -1672,7 +1678,6 @@ check_ipfw_rule_body(ipfw_insn *cmd, int cmd_len, struct rule_check_info *ci) goto bad_size; goto check_action; case O_FORWARD_MAC: /* XXX not implemented yet */ - case O_CHECK_STATE: case O_COUNT: case O_ACCEPT: case O_DENY: @@ -2202,6 +2207,10 @@ check_object_name(ipfw_obj_ntlv *ntlv) case IPFW_TLV_TBL_NAME: error = ipfw_check_table_name(ntlv->name); break; + case IPFW_TLV_STATE_NAME: + /* default: XXX */ + error = check_object_name_generic(ntlv->name); + break; default: error = ENOTSUP; } @@ -2384,7 +2393,10 @@ unref_rule_objects(struct ip_fw_chain *ch, struct ip_fw *rule) KASSERT(no->refcnt > 0, ("refcount for table %d is %d", kidx, no->refcnt)); - no->refcnt--; + if (no->refcnt == 1 && rw->destroy_object != NULL) + rw->destroy_object(ch, no); + else + no->refcnt--; } } @@ -4050,7 +4062,7 @@ ipfw_objhash_lookup_name(struct namedobj_instance *ni, uint32_t set, char *name) * * Returns pointer to found TLV or NULL. */ -static ipfw_obj_ntlv * +ipfw_obj_ntlv * find_name_tlv_type(void *tlvs, int len, uint16_t uidx, uint32_t etlv) { ipfw_obj_ntlv *ntlv;