Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 30 Mar 2017 14:20:27 +0000 (UTC)
From:      "Andrey V. Elsukov" <ae@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-stable@freebsd.org, svn-src-stable-11@freebsd.org
Subject:   svn commit: r316274 - in stable/11: sbin/ipfw sys/netinet sys/netpfil/ipfw
Message-ID:  <201703301420.v2UEKREJ002088@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ae
Date: Thu Mar 30 14:20:27 2017
New Revision: 316274
URL: https://svnweb.freebsd.org/changeset/base/316274

Log:
  MFC r303018:
    Add named dynamic states support to ipfw(4).
  
    The keep-state, limit and check-state now will have additional argument
    flowname. This flowname will be assigned to dynamic rule by keep-state
    or limit opcode. And then can be matched by check-state opcode or
    O_PROBE_STATE internal opcode. To reduce possible breakage and to maximize
    compatibility with old rulesets default flowname introduced.
    It will be assigned to the rules when user has omitted state name in
    keep-state and check-state opcodes. Also if name is ambiguous (can be
    evaluated as rule opcode) it will be replaced to default.
  
    Reviewed by:	julian
    Obtained from:	Yandex LLC
    Relnotes:	yes
    Sponsored by:	Yandex LLC
    Differential Revision:	https://reviews.freebsd.org/D6674
  
  MFC r304087:
    Do not warn about ambiguous state name when we inspect a comment token.
  
  MFC r304089:
    Add an ability to attach comment to check-state rules.
  
  MFC r310727 (by marius):
    Fix a bug in r272840; given that the optlen parameter of setsockopt(2)
    is a 32-bit socklen_t, do_get3() passes the kernel to access the wrong
    32-bit half on big-endian LP64 machines when simply casting the 64-bit
    size_t optlen to a socklen_t pointer.
    While at it and given that the intention of do_get3() apparently is to
    hide/wrap the fact that socket options are used for communication with
    ipfw(4), change the optlen parameter of do_set3() to be of type size_t
    and as such more appropriate than uintptr_t, too.
  
  MFC r315305:
    Change the syntax of ipfw's named states.
  
    Since the state name is an optional argument, it often can conflict
    with other options. To avoid ambiguity now the state name must be
    prefixed with a colon.
  
  Sponsored by:	Yandex LLC

Modified:
  stable/11/sbin/ipfw/ipfw.8
  stable/11/sbin/ipfw/ipfw2.c
  stable/11/sbin/ipfw/ipfw2.h
  stable/11/sys/netinet/ip_fw.h
  stable/11/sys/netpfil/ipfw/ip_fw2.c
  stable/11/sys/netpfil/ipfw/ip_fw_dynamic.c
  stable/11/sys/netpfil/ipfw/ip_fw_private.h
  stable/11/sys/netpfil/ipfw/ip_fw_sockopt.c
Directory Properties:
  stable/11/   (props changed)

Modified: stable/11/sbin/ipfw/ipfw.8
==============================================================================
--- stable/11/sbin/ipfw/ipfw.8	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sbin/ipfw/ipfw.8	Thu Mar 30 14:20:27 2017	(r316274)
@@ -1,7 +1,7 @@
 .\"
 .\" $FreeBSD$
 .\"
-.Dd May 26, 2016
+.Dd March 15, 2017
 .Dt IPFW 8
 .Os
 .Sh NAME
@@ -737,7 +737,7 @@ will be executed when the packet matches
 .It Cm allow | accept | pass | permit
 Allow packets that match rule.
 The search terminates.
-.It Cm check-state
+.It Cm check-state Op Ar :flowname | Cm :any
 Checks the packet against the dynamic ruleset.
 If a match is found, execute the action associated with
 the rule which generated this dynamic rule, otherwise
@@ -752,6 +752,17 @@ rule is found, the dynamic ruleset is ch
 or
 .Cm limit
 rule.
+The
+.Ar :flowname
+is symbolic name assigned to dynamic rule by
+.Cm keep-state
+opcode.
+The special flowname
+.Cm :any
+can be used to ignore states flowname when matching.
+The
+.Cm :default
+keyword is special name used for compatibility with old rulesets.
 .It Cm count
 Update counters for all packets that match rule.
 The search continues with the next rule.
@@ -1574,7 +1585,7 @@ specified in the same way as
 .It Cm ipversion Ar ver
 Matches IP packets whose IP version field is
 .Ar ver .
-.It Cm keep-state
+.It Cm keep-state Op Ar :flowname
 Upon a match, the firewall will create a dynamic rule, whose
 default behaviour is to match bidirectional traffic between
 source and destination IP/port using the same protocol.
@@ -1582,11 +1593,20 @@ The rule has a limited lifetime (control
 .Xr sysctl 8
 variables), and the lifetime is refreshed every time a matching
 packet is found.
+The
+.Ar :flowname
+is used to assign additional to addresses, ports and protocol parameter
+to dynamic rule. It can be used for more accurate matching by
+.Cm check-state
+rule.
+The
+.Cm :default
+keyword is special name used for compatibility with old rulesets.
 .It Cm layer2
 Matches only layer2 packets, i.e., those passed to
 .Nm
 from ether_demux() and ether_output_frame().
-.It Cm limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N
+.It Cm limit Bro Cm src-addr | src-port | dst-addr | dst-port Brc Ar N Op Ar :flowname
 The firewall will only allow
 .Ar N
 connections with the same
@@ -1594,8 +1614,6 @@ set of parameters as specified in the ru
 One or more
 of source and destination addresses and ports can be
 specified.
-Currently,
-only IPv4 flows are supported.
 .It Cm lookup Bro Cm dst-ip | dst-port | src-ip | src-port | uid | jail Brc Ar name
 Search an entry in lookup table
 .Ar name
@@ -2188,6 +2206,12 @@ and
 .Em dst
 are used here only to denote the initial match addresses, but they
 are completely equivalent afterwards).
+Rules created by
+.Cm keep-state
+option also have a
+.Ar :flowname
+taken from it.
+This name is used in matching together with addresses, ports and protocol.
 Dynamic rules will be checked at the first
 .Cm check-state, keep-state
 or
@@ -2196,23 +2220,23 @@ occurrence, and the action performed upo
 as in the parent rule.
 .Pp
 Note that no additional attributes other than protocol and IP addresses
-and ports are checked on dynamic rules.
+and ports and :flowname are checked on dynamic rules.
 .Pp
 The typical use of dynamic rules is to keep a closed firewall configuration,
 but let the first TCP SYN packet from the inside network install a
 dynamic rule for the flow so that packets belonging to that session
 will be allowed through the firewall:
 .Pp
-.Dl "ipfw add check-state"
-.Dl "ipfw add allow tcp from my-subnet to any setup keep-state"
+.Dl "ipfw add check-state :OUTBOUND"
+.Dl "ipfw add allow tcp from my-subnet to any setup keep-state :OUTBOUND"
 .Dl "ipfw add deny tcp from any to any"
 .Pp
 A similar approach can be used for UDP, where an UDP packet coming
 from the inside will install a dynamic rule to let the response through
 the firewall:
 .Pp
-.Dl "ipfw add check-state"
-.Dl "ipfw add allow udp from my-subnet to any keep-state"
+.Dl "ipfw add check-state :OUTBOUND"
+.Dl "ipfw add allow udp from my-subnet to any keep-state :OUTBOUND"
 .Dl "ipfw add deny udp from any to any"
 .Pp
 Dynamic rules expire after some time, which depends on the status

Modified: stable/11/sbin/ipfw/ipfw2.c
==============================================================================
--- stable/11/sbin/ipfw/ipfw2.c	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sbin/ipfw/ipfw2.c	Thu Mar 30 14:20:27 2017	(r316274)
@@ -588,7 +588,7 @@ do_cmd(int optname, void *optval, uintpt
  * Returns 0 on success or errno otherwise.
  */
 int
-do_set3(int optname, ip_fw3_opheader *op3, uintptr_t optlen)
+do_set3(int optname, ip_fw3_opheader *op3, size_t optlen)
 {
 
 	if (co.test_only)
@@ -618,6 +618,7 @@ int
 do_get3(int optname, ip_fw3_opheader *op3, size_t *optlen)
 {
 	int error;
+	socklen_t len;
 
 	if (co.test_only)
 		return (0);
@@ -629,8 +630,9 @@ do_get3(int optname, ip_fw3_opheader *op
 
 	op3->opcode = optname;
 
-	error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3,
-	    (socklen_t *)optlen);
+	len = *optlen;
+	error = getsockopt(ipfw_socket, IPPROTO_IP, IP_FW3, op3, &len);
+	*optlen = len;
 
 	return (error);
 }
@@ -1403,6 +1405,7 @@ show_static_rule(struct cmdline_opts *co
 	int l;
 	ipfw_insn *cmd, *has_eaction = NULL, *tagptr = NULL;
 	const char *comment = NULL;	/* ptr to comment if we have one */
+	const char *ename;
 	int proto = 0;		/* default */
 	int flags = 0;	/* prerequisites */
 	ipfw_insn_log *logptr = NULL; /* set if we find an O_LOG */
@@ -1472,6 +1475,12 @@ show_static_rule(struct cmdline_opts *co
 		switch(cmd->opcode) {
 		case O_CHECK_STATE:
 			bprintf(bp, "check-state");
+			if (cmd->arg1 != 0)
+				ename = object_search_ctlv(fo->tstate,
+				    cmd->arg1, IPFW_TLV_STATE_NAME);
+			else
+				ename = NULL;
+			bprintf(bp, " :%s", ename ? ename: "any");
 			/* avoid printing anything else */
 			flags = HAVE_PROTO | HAVE_SRCIP |
 				HAVE_DSTIP | HAVE_IP;
@@ -1589,8 +1598,6 @@ show_static_rule(struct cmdline_opts *co
 			break;
 
 		case O_EXTERNAL_ACTION: {
-			const char *ename;
-
 			/*
 			 * The external action can consists of two following
 			 * each other opcodes - O_EXTERNAL_ACTION and
@@ -1611,8 +1618,6 @@ show_static_rule(struct cmdline_opts *co
 		}
 
 		case O_EXTERNAL_INSTANCE: {
-			const char *ename;
-
 			if (has_eaction == NULL)
 				break;
 			/*
@@ -2068,6 +2073,9 @@ show_static_rule(struct cmdline_opts *co
 
 			case O_KEEP_STATE:
 				bprintf(bp, " keep-state");
+				bprintf(bp, " :%s",
+				    object_search_ctlv(fo->tstate, cmd->arg1,
+				    IPFW_TLV_STATE_NAME));
 				break;
 
 			case O_LIMIT: {
@@ -2084,6 +2092,9 @@ show_static_rule(struct cmdline_opts *co
 						comma = ",";
 					}
 				bprint_uint_arg(bp, " ", c->conn_limit);
+				bprintf(bp, " :%s",
+				    object_search_ctlv(fo->tstate, cmd->arg1,
+				    IPFW_TLV_STATE_NAME));
 				break;
 			}
 
@@ -2182,7 +2193,10 @@ show_dyn_state(struct cmdline_opts *co, 
 		bprintf(bp, " <-> %s %d", inet_ntop(AF_INET6, &d->id.dst_ip6,
 		    buf, sizeof(buf)), d->id.dst_port);
 	} else
-		bprintf(bp, " UNKNOWN <-> UNKNOWN\n");
+		bprintf(bp, " UNKNOWN <-> UNKNOWN");
+	if (d->kidx != 0)
+		bprintf(bp, " :%s", object_search_ctlv(fo->tstate,
+		    d->kidx, IPFW_TLV_STATE_NAME));
 }
 
 static int
@@ -2823,6 +2837,18 @@ ipfw_check_object_name(const char *name)
 	return (0);
 }
 
+static char *default_state_name = "default";
+static int
+state_check_name(const char *name)
+{
+
+	if (ipfw_check_object_name(name) != 0)
+		return (EINVAL);
+	if (strcmp(name, "any") == 0)
+		return (EINVAL);
+	return (0);
+}
+
 static int
 eaction_check_name(const char *name)
 {
@@ -3685,6 +3711,25 @@ compile_rule(char *av[], uint32_t *rbuf,
 	case TOK_CHECKSTATE:
 		have_state = action;
 		action->opcode = O_CHECK_STATE;
+		if (*av == NULL ||
+		    match_token(rule_options, *av) == TOK_COMMENT) {
+			action->arg1 = pack_object(tstate,
+			    default_state_name, IPFW_TLV_STATE_NAME);
+			break;
+		}
+		if (*av[0] == ':') {
+			if (strcmp(*av + 1, "any") == 0)
+				action->arg1 = 0;
+			else if (state_check_name(*av + 1) == 0)
+				action->arg1 = pack_object(tstate, *av + 1,
+				    IPFW_TLV_STATE_NAME);
+			else
+				errx(EX_DATAERR, "Invalid state name %s",
+				    *av);
+			av++;
+			break;
+		}
+		errx(EX_DATAERR, "Invalid state name %s", *av);
 		break;
 
 	case TOK_ACCEPT:
@@ -4073,8 +4118,17 @@ chkarg:
 		cmd = next_cmd(cmd, &cblen);
 	}
 
-	if (have_state)	/* must be a check-state, we are done */
+	if (have_state)	{ /* must be a check-state, we are done */
+		if (*av != NULL &&
+		    match_token(rule_options, *av) == TOK_COMMENT) {
+			/* check-state has a comment */
+			av++;
+			fill_comment(cmd, av, cblen);
+			cmd = next_cmd(cmd, &cblen);
+			av[0] = NULL;
+		}
 		goto done;
+	}
 
 #define OR_START(target)					\
 	if (av[0] && (*av[0] == '(' || *av[0] == '{')) { 	\
@@ -4509,16 +4563,29 @@ read_options:
 			av++;
 			break;
 
-		case TOK_KEEPSTATE:
+		case TOK_KEEPSTATE: {
+			uint16_t uidx;
+
 			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 && *av[0] == ':') {
+				if (state_check_name(*av + 1) != 0)
+					errx(EX_DATAERR,
+					    "Invalid state name %s", *av);
+				uidx = pack_object(tstate, *av + 1,
+				    IPFW_TLV_STATE_NAME);
+				av++;
+			} else
+				uidx = pack_object(tstate, default_state_name,
+				    IPFW_TLV_STATE_NAME);
 			have_state = cmd;
-			fill_cmd(cmd, O_KEEP_STATE, 0, 0);
+			fill_cmd(cmd, O_KEEP_STATE, 0, uidx);
 			break;
+		}
 
 		case TOK_LIMIT: {
 			ipfw_insn_limit *c = (ipfw_insn_limit *)cmd;
@@ -4549,8 +4616,18 @@ read_options:
 
 			GET_UINT_ARG(c->conn_limit, IPFW_ARG_MIN, IPFW_ARG_MAX,
 			    TOK_LIMIT, rule_options);
-
 			av++;
+
+			if (*av != NULL && *av[0] == ':') {
+				if (state_check_name(*av + 1) != 0)
+					errx(EX_DATAERR,
+					    "Invalid state name %s", *av);
+				cmd->arg1 = pack_object(tstate, *av + 1,
+				    IPFW_TLV_STATE_NAME);
+				av++;
+			} else
+				cmd->arg1 = pack_object(tstate,
+				    default_state_name, IPFW_TLV_STATE_NAME);
 			break;
 		}
 
@@ -4756,7 +4833,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);
 	}
 
@@ -5141,6 +5218,7 @@ static struct _s_x intcmds[] = {
 
 static struct _s_x otypes[] = {
 	{ "EACTION",	IPFW_TLV_EACTION },
+	{ "DYNSTATE",	IPFW_TLV_STATE_NAME },
 	{ NULL, 0 }
 };
 

Modified: stable/11/sbin/ipfw/ipfw2.h
==============================================================================
--- stable/11/sbin/ipfw/ipfw2.h	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sbin/ipfw/ipfw2.h	Thu Mar 30 14:20:27 2017	(r316274)
@@ -299,7 +299,7 @@ void print_flags_buffer(char *buf, size_
 
 struct _ip_fw3_opheader;
 int do_cmd(int optname, void *optval, uintptr_t optlen);
-int do_set3(int optname, struct _ip_fw3_opheader *op3, uintptr_t optlen);
+int do_set3(int optname, struct _ip_fw3_opheader *op3, size_t optlen);
 int do_get3(int optname, struct _ip_fw3_opheader *op3, size_t *optlen);
 
 struct in6_addr;

Modified: stable/11/sys/netinet/ip_fw.h
==============================================================================
--- stable/11/sys/netinet/ip_fw.h	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sys/netinet/ip_fw.h	Thu Mar 30 14:20:27 2017	(r316274)
@@ -683,7 +683,8 @@ struct _ipfw_dyn_rule {
 					/* to generate keepalives)	*/
 	u_int16_t	dyn_type;	/* rule type			*/
 	u_int16_t	count;		/* refcount			*/
-};
+	u_int16_t	kidx;		/* index of named object */
+} __packed __aligned(8);
 
 /*
  * Definitions for IP option names.
@@ -784,6 +785,7 @@ typedef struct  _ipfw_obj_tlv {
 #define	IPFW_TLV_TBLENT_LIST	8
 #define	IPFW_TLV_RANGE		9
 #define	IPFW_TLV_EACTION	10
+#define	IPFW_TLV_STATE_NAME	14
 
 #define	IPFW_TLV_EACTION_BASE	1000
 #define	IPFW_TLV_EACTION_NAME(arg)	(IPFW_TLV_EACTION_BASE + (arg))

Modified: stable/11/sys/netpfil/ipfw/ip_fw2.c
==============================================================================
--- stable/11/sys/netpfil/ipfw/ip_fw2.c	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sys/netpfil/ipfw/ip_fw2.c	Thu Mar 30 14:20:27 2017	(r316274)
@@ -971,6 +971,7 @@ ipfw_chk(struct ip_fw_args *args)
 	 *	MATCH_FORWARD or MATCH_REVERSE otherwise (q != NULL)
 	 */
 	int dyn_dir = MATCH_UNKNOWN;
+	uint16_t dyn_name = 0;
 	ipfw_dyn_rule *q = NULL;
 	struct ip_fw_chain *chain = &V_layer3_chain;
 
@@ -2159,17 +2160,35 @@ do {								\
 				/*
 				 * dynamic rules are checked at the first
 				 * keep-state or check-state occurrence,
-				 * with the result being stored in dyn_dir.
+				 * with the result being stored in dyn_dir
+				 * and dyn_name.
 				 * 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 &&
+				if ((dyn_dir == MATCH_UNKNOWN ||
+				    (dyn_name != 0 &&
+				    dyn_name != cmd->arg1)) &&
 				    (q = ipfw_lookup_dyn_rule(&args->f_id,
 				     &dyn_dir, proto == IPPROTO_TCP ?
-					TCP(ulp) : NULL))
-					!= NULL) {
+				     TCP(ulp): NULL,
+				     (dyn_name = cmd->arg1))) != NULL) {
 					/*
 					 * Found dynamic entry, update stats
 					 * and jump to the 'action' part of

Modified: stable/11/sys/netpfil/ipfw/ip_fw_dynamic.c
==============================================================================
--- stable/11/sys/netpfil/ipfw/ip_fw_dynamic.c	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sys/netpfil/ipfw/ip_fw_dynamic.c	Thu Mar 30 14:20:27 2017	(r316274)
@@ -281,6 +281,200 @@ hash_packet(struct ipfw_flow_id *id, int
 	return (i & (buckets - 1));
 }
 
+#if 0
+#define	DYN_DEBUG(fmt, ...)	do {			\
+	printf("%s: " fmt "\n", __func__, __VA_ARGS__);	\
+} while (0)
+#else
+#define	DYN_DEBUG(fmt, ...)
+#endif
+
+static char *default_state_name = "default";
+struct dyn_state_obj {
+	struct named_object	no;
+	char			name[64];
+};
+
+#define	DYN_STATE_OBJ(ch, cmd)	\
+    ((struct dyn_state_obj *)SRV_OBJECT(ch, (cmd)->arg1))
+/*
+ * Classifier callback.
+ * Return 0 if opcode contains object that should be referenced
+ * or rewritten.
+ */
+static int
+dyn_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype)
+{
+
+	DYN_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1);
+	/* Don't rewrite "check-state any" */
+	if (cmd->arg1 == 0 &&
+	    cmd->opcode == O_CHECK_STATE)
+		return (1);
+
+	*puidx = cmd->arg1;
+	*ptype = 0;
+	return (0);
+}
+
+static void
+dyn_update(ipfw_insn *cmd, uint16_t idx)
+{
+
+	cmd->arg1 = idx;
+	DYN_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1);
+}
+
+static int
+dyn_findbyname(struct ip_fw_chain *ch, struct tid_info *ti,
+    struct named_object **pno)
+{
+	ipfw_obj_ntlv *ntlv;
+	const char *name;
+
+	DYN_DEBUG("uidx %d", ti->uidx);
+	if (ti->uidx != 0) {
+		if (ti->tlvs == NULL)
+			return (EINVAL);
+		/* Search ntlv in the buffer provided by user */
+		ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx,
+		    IPFW_TLV_STATE_NAME);
+		if (ntlv == NULL)
+			return (EINVAL);
+		name = ntlv->name;
+	} else
+		name = default_state_name;
+	/*
+	 * Search named object with corresponding name.
+	 * Since states objects are global - ignore the set value
+	 * and use zero instead.
+	 */
+	*pno = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0,
+	    IPFW_TLV_STATE_NAME, name);
+	/*
+	 * We always return success here.
+	 * The caller will check *pno and mark object as unresolved,
+	 * then it will automatically create "default" object.
+	 */
+	return (0);
+}
+
+static struct named_object *
+dyn_findbykidx(struct ip_fw_chain *ch, uint16_t idx)
+{
+
+	DYN_DEBUG("kidx %d", 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;
+	char *name;
+
+	DYN_DEBUG("uidx %d", ti->uidx);
+	if (ti->uidx != 0) {
+		if (ti->tlvs == NULL)
+			return (EINVAL);
+		ntlv = ipfw_find_name_tlv_type(ti->tlvs, ti->tlen, ti->uidx,
+		    IPFW_TLV_STATE_NAME);
+		if (ntlv == NULL)
+			return (EINVAL);
+		name = ntlv->name;
+	} else
+		name = default_state_name;
+
+	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, name, sizeof(obj->name));
+
+	IPFW_UH_WLOCK(ch);
+	no = ipfw_objhash_lookup_name_type(ni, 0,
+	    IPFW_TLV_STATE_NAME, name);
+	if (no != NULL) {
+		/*
+		 * Object is already created.
+		 * Just return its kidx and bump refcount.
+		 */
+		*pkidx = no->kidx;
+		no->refcnt++;
+		IPFW_UH_WUNLOCK(ch);
+		free(obj, M_IPFW);
+		DYN_DEBUG("\tfound kidx %d", *pkidx);
+		return (0);
+	}
+	if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) {
+		DYN_DEBUG("\talloc_idx failed for %s", name);
+		IPFW_UH_WUNLOCK(ch);
+		free(obj, M_IPFW);
+		return (ENOSPC);
+	}
+	ipfw_objhash_add(ni, &obj->no);
+	IPFW_WLOCK(ch);
+	SRV_OBJECT(ch, obj->no.kidx) = obj;
+	IPFW_WUNLOCK(ch);
+	obj->no.refcnt++;
+	*pkidx = obj->no.kidx;
+	IPFW_UH_WUNLOCK(ch);
+	DYN_DEBUG("\tcreated kidx %d", *pkidx);
+	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);
+
+	KASSERT(no->refcnt == 1,
+	    ("Destroying object '%s' (type %u, idx %u) with refcnt %u",
+	    no->name, no->etlv, no->kidx, no->refcnt));
+
+	DYN_DEBUG("kidx %d", no->kidx);
+	IPFW_WLOCK(ch);
+	obj = SRV_OBJECT(ch, no->kidx);
+	SRV_OBJECT(ch, 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
+	},
+	{
+		O_LIMIT, IPFW_TLV_STATE_NAME,
+		dyn_classify, dyn_update,
+		dyn_findbyname, dyn_findbykidx,
+		dyn_create, dyn_destroy
+	},
+};
 /**
  * Print customizable flow id description via log(9) facility.
  */
@@ -402,7 +596,7 @@ dyn_update_proto_state(ipfw_dyn_rule *q,
  */
 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.
@@ -415,10 +609,13 @@ lookup_dyn_rule_locked(struct ipfw_flow_
 
 	dir = MATCH_NONE;
 	for (prev = NULL, q = V_ipfw_dyn_v[i].head; q; prev = q, q = q->next) {
-		if (q->dyn_type == O_LIMIT_PARENT && q->count)
+		if (q->dyn_type == O_LIMIT_PARENT)
+			continue;
+
+		if (pkt->proto != q->id.proto)
 			continue;
 
-		if (pkt->proto != q->id.proto || q->dyn_type == O_LIMIT_PARENT)
+		if (kidx != 0 && kidx != q->kidx)
 			continue;
 
 		if (IS_IP6_FLOW_ID(pkt)) {
@@ -472,7 +669,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;
@@ -480,7 +677,7 @@ ipfw_lookup_dyn_rule(struct ipfw_flow_id
 	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 */
@@ -590,7 +787,8 @@ resize_dynamic_table(struct ip_fw_chain 
  * - "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)
+add_dyn_rule(struct ipfw_flow_id *id, int i, uint8_t dyn_type,
+    struct ip_fw *rule, uint16_t kidx)
 {
 	ipfw_dyn_rule *r;
 
@@ -626,7 +824,7 @@ add_dyn_rule(struct ipfw_flow_id *id, in
 	r->dyn_type = dyn_type;
 	IPFW_ZERO_DYN_COUNTER(r);
 	r->count = 0;
-
+	r->kidx = kidx;
 	r->bucket = i;
 	r->next = V_ipfw_dyn_v[i].head;
 	V_ipfw_dyn_v[i].head = r;
@@ -639,7 +837,8 @@ add_dyn_rule(struct ipfw_flow_id *id, in
  * If the lookup fails, then install one.
  */
 static ipfw_dyn_rule *
-lookup_dyn_parent(struct ipfw_flow_id *pkt, int *pindex, struct ip_fw *rule)
+lookup_dyn_parent(struct ipfw_flow_id *pkt, int *pindex, struct ip_fw *rule,
+    uint16_t kidx)
 {
 	ipfw_dyn_rule *q;
 	int i, is_v6;
@@ -650,7 +849,8 @@ lookup_dyn_parent(struct ipfw_flow_id *p
 	IPFW_BUCK_LOCK(i);
 	for (q = V_ipfw_dyn_v[i].head ; q != NULL ; q=q->next)
 		if (q->dyn_type == O_LIMIT_PARENT &&
-		    rule== q->rule &&
+		    kidx == q->kidx &&
+		    rule == q->rule &&
 		    pkt->proto == q->id.proto &&
 		    pkt->src_port == q->id.src_port &&
 		    pkt->dst_port == q->id.dst_port &&
@@ -672,7 +872,7 @@ lookup_dyn_parent(struct ipfw_flow_id *p
 		}
 
 	/* Add virtual limiting rule */
-	return add_dyn_rule(pkt, i, O_LIMIT_PARENT, rule);
+	return add_dyn_rule(pkt, i, O_LIMIT_PARENT, rule, kidx);
 }
 
 /**
@@ -688,13 +888,14 @@ ipfw_install_state(struct ip_fw_chain *c
 	ipfw_dyn_rule *q;
 	int i;
 
-	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_OBJ(chain, &cmd->o)->name));)
+
 	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(
 		if (last_log != time_uptime) {
@@ -715,7 +916,8 @@ ipfw_install_state(struct ip_fw_chain *c
 
 	switch (cmd->o.opcode) {
 	case O_KEEP_STATE:	/* bidir rule */
-		q = add_dyn_rule(&args->f_id, i, O_KEEP_STATE, rule);
+		q = add_dyn_rule(&args->f_id, i, O_KEEP_STATE, rule,
+		    cmd->o.arg1);
 		break;
 
 	case O_LIMIT: {		/* limit number of sessions */
@@ -766,7 +968,8 @@ ipfw_install_state(struct ip_fw_chain *c
 		 */
 		IPFW_BUCK_UNLOCK(i);
 
-		if ((parent = lookup_dyn_parent(&id, &pindex, rule)) == NULL) {
+		parent = lookup_dyn_parent(&id, &pindex, rule, cmd->o.arg1);
+		if (parent == NULL) {
 			printf("ipfw: %s: add parent failed\n", __func__);
 			IPFW_BUCK_UNLOCK(pindex);
 			return (1);
@@ -794,7 +997,7 @@ ipfw_install_state(struct ip_fw_chain *c
 
 		IPFW_BUCK_LOCK(i);
 		q = add_dyn_rule(&args->f_id, i, O_LIMIT,
-		    (struct ip_fw *)parent);
+		    (struct ip_fw *)parent, cmd->o.arg1);
 		if (q == NULL) {
 			/* Decrement index and notify caller */
 			IPFW_BUCK_UNLOCK(i);
@@ -1411,6 +1614,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
@@ -1422,6 +1626,7 @@ ipfw_dyn_uninit(int pass)
 		callout_drain(&V_ipfw_timeout);
 		return;
 	}
+	IPFW_DEL_OBJ_REWRITER(IS_DEFAULT_VNET(curvnet), dyn_opcodes);
 
 	if (V_ipfw_dyn_v != NULL) {
 		/*

Modified: stable/11/sys/netpfil/ipfw/ip_fw_private.h
==============================================================================
--- stable/11/sys/netpfil/ipfw/ip_fw_private.h	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sys/netpfil/ipfw/ip_fw_private.h	Thu Mar 30 14:20:27 2017	(r316274)
@@ -189,7 +189,7 @@ struct mbuf *ipfw_send_pkt(struct mbuf *
 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);

Modified: stable/11/sys/netpfil/ipfw/ip_fw_sockopt.c
==============================================================================
--- stable/11/sys/netpfil/ipfw/ip_fw_sockopt.c	Thu Mar 30 12:51:44 2017	(r316273)
+++ stable/11/sys/netpfil/ipfw/ip_fw_sockopt.c	Thu Mar 30 14:20:27 2017	(r316274)
@@ -1693,6 +1693,10 @@ check_ipfw_rule_body(ipfw_insn *cmd, int
 		switch (cmd->opcode) {
 		case O_PROBE_STATE:
 		case O_KEEP_STATE:
+			if (cmdlen != F_INSN_SIZE(ipfw_insn))
+				goto bad_size;
+			ci->object_opcodes++;
+			break;
 		case O_PROTO:
 		case O_IP_SRC_ME:
 		case O_IP_DST_ME:
@@ -1790,6 +1794,7 @@ check_ipfw_rule_body(ipfw_insn *cmd, int
 		case O_LIMIT:
 			if (cmdlen != F_INSN_SIZE(ipfw_insn_limit))
 				goto bad_size;
+			ci->object_opcodes++;
 			break;
 
 		case O_LOG:
@@ -1922,8 +1927,10 @@ check_ipfw_rule_body(ipfw_insn *cmd, int
 			if (cmdlen != F_INSN_SIZE(ipfw_insn_nat))
  				goto bad_size;		
  			goto check_action;
-		case O_FORWARD_MAC: /* XXX not implemented yet */
 		case O_CHECK_STATE:
+			ci->object_opcodes++;
+			/* FALLTHROUGH */
+		case O_FORWARD_MAC: /* XXX not implemented yet */
 		case O_COUNT:
 		case O_ACCEPT:
 		case O_DENY:



Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?201703301420.v2UEKREJ002088>