Skip site navigation (1)Skip section navigation (2)
Date:      Thu, 14 Apr 2016 22:51:23 +0000 (UTC)
From:      "Andrey V. Elsukov" <ae@FreeBSD.org>
To:        src-committers@freebsd.org, svn-src-all@freebsd.org, svn-src-head@freebsd.org
Subject:   svn commit: r298016 - in head: sbin/ipfw sys/conf sys/modules/ipfw sys/netinet sys/netpfil/ipfw
Message-ID:  <201604142251.u3EMpNO2072606@repo.freebsd.org>

next in thread | raw e-mail | index | archive | help
Author: ae
Date: Thu Apr 14 22:51:23 2016
New Revision: 298016
URL: https://svnweb.freebsd.org/changeset/base/298016

Log:
  Add External Actions KPI to ipfw(9).
  
  It allows implementing loadable kernel modules with new actions and
  without needing to modify kernel headers and ipfw(8). The module
  registers its action handler and keyword string, that will be used
  as action name. Using generic syntax user can add rules with this
  action. Also ipfw(8) can be easily modified to extend basic syntax
  for external actions, that become a part base system.
  Sample modules will coming soon.
  
  Obtained from:	Yandex LLC
  Sponsored by:	Yandex LLC

Added:
  head/sys/netpfil/ipfw/ip_fw_eaction.c   (contents, props changed)
Modified:
  head/sbin/ipfw/ipfw2.c
  head/sbin/ipfw/ipfw2.h
  head/sbin/ipfw/tables.c
  head/sys/conf/files
  head/sys/modules/ipfw/Makefile
  head/sys/netinet/ip_fw.h
  head/sys/netpfil/ipfw/ip_fw2.c
  head/sys/netpfil/ipfw/ip_fw_private.h
  head/sys/netpfil/ipfw/ip_fw_sockopt.c
  head/sys/netpfil/ipfw/ip_fw_table_value.c

Modified: head/sbin/ipfw/ipfw2.c
==============================================================================
--- head/sbin/ipfw/ipfw2.c	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sbin/ipfw/ipfw2.c	Thu Apr 14 22:51:23 2016	(r298016)
@@ -234,6 +234,9 @@ static struct _s_x ether_types[] = {
 	{ NULL,		0 }
 };
 
+static struct _s_x rule_eactions[] = {
+	{ NULL, 0 }	/* terminator */
+};
 
 static struct _s_x rule_actions[] = {
 	{ "accept",		TOK_ACCEPT },
@@ -265,6 +268,7 @@ static struct _s_x rule_actions[] = {
 	{ "setdscp",		TOK_SETDSCP },
 	{ "call",		TOK_CALL },
 	{ "return",		TOK_RETURN },
+	{ "eaction",		TOK_EACTION },
 	{ NULL, 0 }	/* terminator */
 };
 
@@ -381,6 +385,8 @@ static uint16_t pack_table(struct tidx *
 
 static char *table_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx);
 static void object_sort_ctlv(ipfw_obj_ctlv *ctlv);
+static char *object_search_ctlv(ipfw_obj_ctlv *ctlv, uint16_t idx,
+    uint16_t type);
 
 /*
  * Simple string buffer API.
@@ -634,7 +640,7 @@ do_get3(int optname, ip_fw3_opheader *op
  * with the string (-1 in case of failure).
  */
 int
-match_token(struct _s_x *table, char *string)
+match_token(struct _s_x *table, const char *string)
 {
 	struct _s_x *pt;
 	uint i = strlen(string);
@@ -646,7 +652,7 @@ match_token(struct _s_x *table, char *st
 }
 
 /**
- * match_token takes a table and a string, returns the value associated
+ * match_token_relaxed takes a table and a string, returns the value associated
  * with the string for the best match.
  *
  * Returns:
@@ -655,7 +661,7 @@ match_token(struct _s_x *table, char *st
  * -2 if more than one records match @string.
  */
 int
-match_token_relaxed(struct _s_x *table, char *string)
+match_token_relaxed(struct _s_x *table, const char *string)
 {
 	struct _s_x *pt, *m;
 	int i, c;
@@ -676,6 +682,18 @@ match_token_relaxed(struct _s_x *table, 
 	return (c > 0 ? -2: -1);
 }
 
+int
+get_token(struct _s_x *table, const char *string, const char *errbase)
+{
+	int tcmd;
+
+	if ((tcmd = match_token_relaxed(table, string)) < 0)
+		errx(EX_USAGE, "%s %s %s",
+		    (tcmd == 0) ? "invalid" : "ambiguous", errbase, string);
+
+	return (tcmd);
+}
+
 /**
  * match_value takes a table and a value, returns the string associated
  * with the value (NULL in case of failure).
@@ -1383,7 +1401,7 @@ show_static_rule(struct cmdline_opts *co
 {
 	static int twidth = 0;
 	int l;
-	ipfw_insn *cmd, *tagptr = NULL;
+	ipfw_insn *cmd, *has_eaction = NULL, *tagptr = NULL;
 	const char *comment = NULL;	/* ptr to comment if we have one */
 	int proto = 0;		/* default */
 	int flags = 0;	/* prerequisites */
@@ -1567,6 +1585,52 @@ show_static_rule(struct cmdline_opts *co
 			bprint_uint_arg(bp, "setfib ", cmd->arg1 & 0x7FFF);
  			break;
 
+		case O_EXTERNAL_ACTION: {
+			const char *ename;
+
+			/*
+			 * The external action can consists of two following
+			 * each other opcodes - O_EXTERNAL_ACTION and
+			 * O_EXTERNAL_INSTANCE. The first contains the ID of
+			 * name of external action. The second contains the ID
+			 * of name of external action instance.
+			 * NOTE: in case when external action has no named
+			 * instances support, the second opcode isn't needed.
+			 */
+			has_eaction = cmd;
+			ename = object_search_ctlv(fo->tstate, cmd->arg1,
+			    IPFW_TLV_EACTION);
+			if (match_token(rule_eactions, ename) != -1)
+				bprintf(bp, "%s", ename);
+			else
+				bprintf(bp, "eaction %s", ename);
+			break;
+		}
+
+		case O_EXTERNAL_INSTANCE: {
+			const char *ename;
+
+			if (has_eaction == NULL)
+				break;
+			/*
+			 * XXX: we need to teach ipfw(9) to rewrite opcodes
+			 * in the user buffer on rule addition. When we add
+			 * the rule, we specify zero TLV type for
+			 * O_EXTERNAL_INSTANCE object. To show correct
+			 * rule after `ipfw add` we need to search instance
+			 * name with zero type. But when we do `ipfw show`
+			 * we calculate TLV type using IPFW_TLV_EACTION_NAME()
+			 * macro.
+			 */
+			ename = object_search_ctlv(fo->tstate, cmd->arg1, 0);
+			if (ename == NULL)
+				ename = object_search_ctlv(fo->tstate,
+				    cmd->arg1,
+				    IPFW_TLV_EACTION_NAME(has_eaction->arg1));
+			bprintf(bp, " %s", ename);
+			break;
+		}
+
 		case O_SETDSCP:
 		    {
 			const char *code;
@@ -2730,6 +2794,42 @@ struct tidx {
 	uint8_t set;
 };
 
+int
+ipfw_check_object_name(const char *name)
+{
+	int c, i, l;
+
+	/*
+	 * Check that name is null-terminated and contains
+	 * valid symbols only. Valid mask is:
+	 * [a-zA-Z0-9\-_\.]{1,63}
+	 */
+	l = strlen(name);
+	if (l == 0 || l >= 64)
+		return (EINVAL);
+	for (i = 0; i < l; i++) {
+		c = name[i];
+		if (isalpha(c) || isdigit(c) || c == '_' ||
+		    c == '-' || c == '.')
+			continue;
+		return (EINVAL);
+	}
+	return (0);
+}
+
+static int
+eaction_check_name(const char *name)
+{
+
+	if (ipfw_check_object_name(name) != 0)
+		return (EINVAL);
+	/* Restrict some 'special' names */
+	if (match_token(rule_actions, name) != -1 &&
+	    match_token(rule_action_params, name) != -1)
+		return (EINVAL);
+	return (0);
+}
+
 static uint16_t
 pack_object(struct tidx *tstate, char *name, int otype)
 {
@@ -3833,7 +3933,46 @@ chkarg:
 		break;
 
 	default:
-		errx(EX_DATAERR, "invalid action %s\n", av[-1]);
+		av--;
+		if (match_token(rule_eactions, *av) == -1)
+			errx(EX_DATAERR, "invalid action %s\n", *av);
+		/*
+		 * External actions support.
+		 * XXX: we support only syntax with instance name.
+		 *	For known external actions (from rule_eactions list)
+		 *	we can handle syntax directly. But with `eaction'
+		 *	keyword we can use only `eaction <name> <instance>'
+		 *	syntax.
+		 */
+	case TOK_EACTION: {
+		uint16_t idx;
+
+		NEED1("Missing eaction name");
+		if (eaction_check_name(*av) != 0)
+			errx(EX_DATAERR, "Invalid eaction name %s", *av);
+		idx = pack_object(tstate, *av, IPFW_TLV_EACTION);
+		if (idx == 0)
+			errx(EX_DATAERR, "pack_object failed");
+		fill_cmd(action, O_EXTERNAL_ACTION, 0, idx);
+		av++;
+		NEED1("Missing eaction instance name");
+		action = next_cmd(action, &ablen);
+		action->len = 1;
+		CHECK_ACTLEN;
+		if (eaction_check_name(*av) != 0)
+			errx(EX_DATAERR, "Invalid eaction instance name %s",
+			    *av);
+		/*
+		 * External action instance object has TLV type depended
+		 * from the external action name object index. Since we
+		 * currently don't know this index, use zero as TLV type.
+		 */
+		idx = pack_object(tstate, *av, 0);
+		if (idx == 0)
+			errx(EX_DATAERR, "pack_object failed");
+		fill_cmd(action, O_EXTERNAL_INSTANCE, 0, idx);
+		av++;
+		}
 	}
 	action = next_cmd(action, &ablen);
 
@@ -4758,7 +4897,7 @@ object_search_ctlv(ipfw_obj_ctlv *ctlv, 
 	ntlv = bsearch(&key, (ctlv + 1), ctlv->count, ctlv->objsize,
 	    compare_object_kntlv);
 
-	if (ntlv != 0)
+	if (ntlv != NULL)
 		return (ntlv->name);
 
 	return (NULL);
@@ -5019,7 +5158,7 @@ ipfw_list_objects(int ac, char *av[])
 		printf("There are no objects\n");
 	ntlv = (ipfw_obj_ntlv *)(olh + 1);
 	for (i = 0; i < olh->count; i++) {
-		printf(" kidx: %4d\ttype: %2d\tname: %s\n", ntlv->idx,
+		printf(" kidx: %4d\ttype: %6d\tname: %s\n", ntlv->idx,
 		    ntlv->head.type, ntlv->name);
 		ntlv++;
 	}

Modified: head/sbin/ipfw/ipfw2.h
==============================================================================
--- head/sbin/ipfw/ipfw2.h	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sbin/ipfw/ipfw2.h	Thu Apr 14 22:51:23 2016	(r298016)
@@ -83,6 +83,7 @@ enum tokens {
 
 	TOK_ACCEPT,
 	TOK_COUNT,
+	TOK_EACTION,
 	TOK_PIPE,
 	TOK_LINK,
 	TOK_QUEUE,
@@ -261,8 +262,9 @@ int _substrcmp2(const char *str1, const 
 int stringnum_cmp(const char *a, const char *b);
 
 /* utility functions */
-int match_token(struct _s_x *table, char *string);
-int match_token_relaxed(struct _s_x *table, char *string);
+int match_token(struct _s_x *table, const char *string);
+int match_token_relaxed(struct _s_x *table, const char *string);
+int get_token(struct _s_x *table, const char *string, const char *errbase);
 char const *match_value(struct _s_x *p, int value);
 size_t concat_tokens(char *buf, size_t bufsize, struct _s_x *table,
     char *delimiter);
@@ -313,6 +315,7 @@ void ipfw_flush(int force);
 void ipfw_zero(int ac, char *av[], int optname);
 void ipfw_list(int ac, char *av[], int show_counters);
 void ipfw_internal_handler(int ac, char *av[]);
+int ipfw_check_object_name(const char *name);
 
 #ifdef PF
 /* altq.c */
@@ -345,7 +348,7 @@ int fill_ext6hdr(struct _ipfw_insn *cmd,
 
 /* tables.c */
 struct _ipfw_obj_ctlv;
-int table_check_name(char *tablename);
+int table_check_name(const char *tablename);
 void ipfw_list_ta(int ac, char *av[]);
 void ipfw_list_values(int ac, char *av[]);
 

Modified: head/sbin/ipfw/tables.c
==============================================================================
--- head/sbin/ipfw/tables.c	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sbin/ipfw/tables.c	Thu Apr 14 22:51:23 2016	(r298016)
@@ -53,8 +53,8 @@ static void table_lock(ipfw_obj_header *
 static int table_swap(ipfw_obj_header *oh, char *second);
 static int table_get_info(ipfw_obj_header *oh, ipfw_xtable_info *i);
 static int table_show_info(ipfw_xtable_info *i, void *arg);
-static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set,
-    uint16_t uidx);
+static void table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name,
+    uint32_t set, uint16_t uidx);
 
 static int table_flush_one(ipfw_xtable_info *i, void *arg);
 static int table_show_one(ipfw_xtable_info *i, void *arg);
@@ -130,18 +130,6 @@ lookup_host (char *host, struct in_addr 
 	return(0);
 }
 
-static int
-get_token(struct _s_x *table, char *string, char *errbase)
-{
-	int tcmd;
-
-	if ((tcmd = match_token_relaxed(table, string)) < 0)
-		errx(EX_USAGE, "%s %s %s",
-		    (tcmd == 0) ? "invalid" : "ambiguous", errbase, string);
-
-	return (tcmd);
-}
-
 /*
  * This one handles all table-related commands
  * 	ipfw table NAME create ...
@@ -293,7 +281,8 @@ ipfw_table_handler(int ac, char *av[])
 }
 
 static void
-table_fill_ntlv(ipfw_obj_ntlv *ntlv, char *name, uint32_t set, uint16_t uidx)
+table_fill_ntlv(ipfw_obj_ntlv *ntlv, const char *name, uint32_t set,
+    uint16_t uidx)
 {
 
 	ntlv->head.type = IPFW_TLV_TBL_NAME;
@@ -1994,30 +1983,14 @@ ipfw_list_values(int ac, char *av[])
 }
 
 int
-table_check_name(char *tablename)
+table_check_name(const char *tablename)
 {
-	int c, i, l;
 
-	/*
-	 * Check if tablename is null-terminated and contains
-	 * valid symbols only. Valid mask is:
-	 * [a-zA-Z0-9\-_\.]{1,63}
-	 */
-	l = strlen(tablename);
-	if (l == 0 || l >= 64)
+	if (ipfw_check_object_name(tablename) != 0)
 		return (EINVAL);
-	for (i = 0; i < l; i++) {
-		c = tablename[i];
-		if (isalpha(c) || isdigit(c) || c == '_' ||
-		    c == '-' || c == '.')
-			continue;
-		return (EINVAL);	
-	}
-
 	/* Restrict some 'special' names */
 	if (strcmp(tablename, "all") == 0)
 		return (EINVAL);
-
 	return (0);
 }
 

Modified: head/sys/conf/files
==============================================================================
--- head/sys/conf/files	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/conf/files	Thu Apr 14 22:51:23 2016	(r298016)
@@ -3749,6 +3749,7 @@ netpfil/ipfw/ip_dn_io.c		optional inet d
 netpfil/ipfw/ip_dn_glue.c	optional inet dummynet
 netpfil/ipfw/ip_fw2.c		optional inet ipfirewall
 netpfil/ipfw/ip_fw_dynamic.c	optional inet ipfirewall
+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
 netpfil/ipfw/ip_fw_sockopt.c	optional inet ipfirewall

Modified: head/sys/modules/ipfw/Makefile
==============================================================================
--- head/sys/modules/ipfw/Makefile	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/modules/ipfw/Makefile	Thu Apr 14 22:51:23 2016	(r298016)
@@ -4,7 +4,7 @@
 
 KMOD=	ipfw
 SRCS=	ip_fw2.c ip_fw_pfil.c
-SRCS+=	ip_fw_dynamic.c ip_fw_log.c
+SRCS+=	ip_fw_dynamic.c ip_fw_log.c ip_fw_eaction.c
 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 opt_ipsec.h

Modified: head/sys/netinet/ip_fw.h
==============================================================================
--- head/sys/netinet/ip_fw.h	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/netinet/ip_fw.h	Thu Apr 14 22:51:23 2016	(r298016)
@@ -256,10 +256,12 @@ enum ipfw_opcodes {		/* arguments (4 byt
 	O_SETDSCP,		/* arg1=DSCP value */
 	O_IP_FLOW_LOOKUP,	/* arg1=table number, u32=value	*/
 
+	O_EXTERNAL_ACTION,	/* arg1=id of external action handler */
+	O_EXTERNAL_INSTANCE,	/* arg1=id of eaction handler instance */
+
 	O_LAST_OPCODE		/* not an opcode!		*/
 };
 
-
 /*
  * The extension header are filtered only for presence using a bit
  * vector with a flag for each header.
@@ -780,6 +782,10 @@ typedef struct  _ipfw_obj_tlv {
 #define	IPFW_TLV_RULE_ENT	7
 #define	IPFW_TLV_TBLENT_LIST	8
 #define	IPFW_TLV_RANGE		9
+#define	IPFW_TLV_EACTION	10
+
+#define	IPFW_TLV_EACTION_BASE	1000
+#define	IPFW_TLV_EACTION_NAME(arg)	(IPFW_TLV_EACTION_BASE + (arg))
 
 /* Object name TLV */
 typedef struct _ipfw_obj_ntlv {

Modified: head/sys/netpfil/ipfw/ip_fw2.c
==============================================================================
--- head/sys/netpfil/ipfw/ip_fw2.c	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/netpfil/ipfw/ip_fw2.c	Thu Apr 14 22:51:23 2016	(r298016)
@@ -2542,6 +2542,11 @@ do {								\
 				done = 1;	/* exit outer loop */
 				break;
 			}
+			case O_EXTERNAL_ACTION:
+				l = 0; /* in any case exit inner loop */
+				retval = ipfw_run_eaction(chain, args,
+				    cmd, &done);
+				break;
 
 			default:
 				panic("-- unknown opcode %d\n", cmd->opcode);
@@ -2766,6 +2771,7 @@ vnet_ipfw_init(const void *unused)
 
 	IPFW_LOCK_INIT(chain);
 	ipfw_dyn_init(chain);
+	ipfw_eaction_init(chain, first);
 #ifdef LINEAR_SKIPTO
 	ipfw_init_skipto_cache(chain);
 #endif
@@ -2830,6 +2836,7 @@ vnet_ipfw_uninit(const void *unused)
 	IPFW_WUNLOCK(chain);
 	IPFW_UH_WUNLOCK(chain);
 	ipfw_destroy_tables(chain, last);
+	ipfw_eaction_uninit(chain, last);
 	if (reap != NULL)
 		ipfw_reap_rules(reap);
 	vnet_ipfw_iface_destroy(chain);

Added: head/sys/netpfil/ipfw/ip_fw_eaction.c
==============================================================================
--- /dev/null	00:00:00 1970	(empty, because file is newly added)
+++ head/sys/netpfil/ipfw/ip_fw_eaction.c	Thu Apr 14 22:51:23 2016	(r298016)
@@ -0,0 +1,369 @@
+/*-
+ * Copyright (c) 2016 Yandex LLC
+ * Copyright (c) 2016 Andrey V. Elsukov <ae@FreeBSD.org>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/malloc.h>
+#include <sys/kernel.h>
+#include <sys/hash.h>
+#include <sys/lock.h>
+#include <sys/rwlock.h>
+#include <sys/rmlock.h>
+#include <sys/socket.h>
+#include <sys/socketvar.h>
+#include <sys/queue.h>
+#include <net/pfil.h>
+
+#include <net/if.h>	/* ip_fw.h requires IFNAMSIZ */
+#include <netinet/in.h>
+#include <netinet/ip_var.h>	/* struct ipfw_rule_ref */
+#include <netinet/ip_fw.h>
+
+#include <netpfil/ipfw/ip_fw_private.h>
+
+#include "opt_ipfw.h"
+
+/*
+ * External actions support for ipfw.
+ *
+ * This code provides KPI for implementing loadable modules, that
+ * can provide handlers for external action opcodes in the ipfw's
+ * rules.
+ * Module should implement opcode handler with type ipfw_eaction_t.
+ * This handler will be called by ipfw_chk() function when
+ * O_EXTERNAL_ACTION opcode will be matched. The handler must return
+ * value used as return value in ipfw_chk(), i.e. IP_FW_PASS,
+ * IP_FW_DENY (see ip_fw_private.h).
+ * Also the last argument must be set by handler. If it is zero,
+ * the search continues to the next rule. If it has non zero value,
+ * the search terminates.
+ *
+ * The module that implements external action should register its
+ * handler and name with ipfw_add_eaction() function.
+ * This function will return eaction_id, that can be used by module.
+ *
+ * It is possible to pass some additional information to external
+ * action handler via the O_EXTERNAL_INSTANCE opcode. This opcode
+ * will be next after the O_EXTERNAL_ACTION opcode. cmd->arg1 will
+ * contain index of named object related to instance of external action.
+ *
+ * In case when eaction module uses named instances, it should register
+ * opcode rewriting routines for O_EXTERNAL_INSTANCE opcode. The
+ * classifier callback can look back into O_EXTERNAL_ACTION opcode (it
+ * must be in the (ipfw_insn *)(cmd - 1)). By arg1 from O_EXTERNAL_ACTION
+ * it can deteremine eaction_id and compare it with its own.
+ * The macro IPFW_TLV_EACTION_NAME(eaction_id) can be used to deteremine
+ * the type of named_object related to external action instance.
+ *
+ * On module unload handler should be deregistered with ipfw_del_eaction()
+ * function using known eaction_id.
+ */
+
+struct eaction_obj {
+	struct named_object	no;
+	ipfw_eaction_t		*handler;
+	char			name[64];
+};
+
+#define	EACTION_OBJ(ch, cmd)			\
+    ((struct eaction_obj *)SRV_OBJECT((ch), (cmd)->arg1))
+
+#if 0
+#define	EACTION_DEBUG(fmt, ...)	do {			\
+	printf("%s: " fmt "\n", __func__, ## __VA_ARGS__);	\
+} while (0)
+#else
+#define	EACTION_DEBUG(fmt, ...)
+#endif
+
+const char *default_eaction_typename = "drop";
+static int
+default_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args,
+    ipfw_insn *cmd, int *done)
+{
+
+	*done = 1; /* terminate the search */
+	return (IP_FW_DENY);
+}
+
+/*
+ * Opcode rewriting callbacks.
+ */
+static int
+eaction_classify(ipfw_insn *cmd, uint16_t *puidx, uint8_t *ptype)
+{
+
+	EACTION_DEBUG("opcode %d, arg1 %d", cmd->opcode, cmd->arg1);
+	*puidx = cmd->arg1;
+	*ptype = 0;
+	return (0);
+}
+
+static void
+eaction_update(ipfw_insn *cmd, uint16_t idx)
+{
+
+	cmd->arg1 = idx;
+	EACTION_DEBUG("opcode %d, arg1 -> %d", cmd->opcode, cmd->arg1);
+}
+
+static int
+eaction_findbyname(struct ip_fw_chain *ch, struct tid_info *ti,
+    struct named_object **pno)
+{
+
+	EACTION_DEBUG("uidx %u, type %u", ti->uidx, ti->type);
+	return (ipfw_objhash_find_type(CHAIN_TO_SRV(ch), ti,
+	    IPFW_TLV_EACTION, pno));
+}
+
+static struct named_object *
+eaction_findbykidx(struct ip_fw_chain *ch, uint16_t idx)
+{
+
+	EACTION_DEBUG("kidx %u", idx);
+	return (ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), idx));
+}
+
+static int
+eaction_create_compat(struct ip_fw_chain *ch, struct tid_info *ti,
+    uint16_t *pkidx)
+{
+
+	return (EOPNOTSUPP);
+}
+
+static struct opcode_obj_rewrite eaction_opcodes[] = {
+	{
+		O_EXTERNAL_ACTION, IPFW_TLV_EACTION,
+		eaction_classify, eaction_update,
+		eaction_findbyname, eaction_findbykidx,
+		eaction_create_compat
+	},
+};
+
+static int
+create_eaction_obj(struct ip_fw_chain *ch, ipfw_eaction_t handler,
+    const char *name, uint16_t *eaction_id)
+{
+	struct namedobj_instance *ni;
+	struct eaction_obj *obj;
+
+	IPFW_UH_UNLOCK_ASSERT(ch);
+
+	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_EACTION;
+	obj->handler = handler;
+	strlcpy(obj->name, name, sizeof(obj->name));
+
+	IPFW_UH_WLOCK(ch);
+	if (ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION,
+	    name) != NULL) {
+		/*
+		 * Object is already created.
+		 * We don't allow eactions with the same name.
+		 */
+		IPFW_UH_WUNLOCK(ch);
+		free(obj, M_IPFW);
+		EACTION_DEBUG("External action with typename "
+		    "'%s' already exists", name);
+		return (EEXIST);
+	}
+	if (ipfw_objhash_alloc_idx(ni, &obj->no.kidx) != 0) {
+		IPFW_UH_WUNLOCK(ch);
+		free(obj, M_IPFW);
+		EACTION_DEBUG("alloc_idx failed");
+		return (ENOSPC);
+	}
+	ipfw_objhash_add(ni, &obj->no);
+	IPFW_WLOCK(ch);
+	SRV_OBJECT(ch, obj->no.kidx) = obj;
+	IPFW_WUNLOCK(ch);
+	obj->no.refcnt++;
+	IPFW_UH_WUNLOCK(ch);
+
+	if (eaction_id != NULL)
+		*eaction_id = obj->no.kidx;
+	return (0);
+}
+
+static void
+destroy_eaction_obj(struct ip_fw_chain *ch, struct named_object *no)
+{
+	struct namedobj_instance *ni;
+	struct eaction_obj *obj;
+
+	IPFW_UH_WLOCK_ASSERT(ch);
+
+	ni = CHAIN_TO_SRV(ch);
+	IPFW_WLOCK(ch);
+	obj = SRV_OBJECT(ch, no->kidx);
+	SRV_OBJECT(ch, no->kidx) = NULL;
+	IPFW_WUNLOCK(ch);
+	ipfw_objhash_del(ni, no);
+	ipfw_objhash_free_idx(ni, no->kidx);
+	free(obj, M_IPFW);
+}
+
+/*
+ * Resets all eaction opcodes to default handlers.
+ */
+static void
+reset_eaction_obj(struct ip_fw_chain *ch, uint16_t eaction_id)
+{
+	struct named_object *no;
+	struct ip_fw *rule;
+	ipfw_insn *cmd;
+	int i;
+
+	IPFW_UH_WLOCK_ASSERT(ch);
+
+	no = ipfw_objhash_lookup_name_type(CHAIN_TO_SRV(ch), 0,
+	    IPFW_TLV_EACTION, default_eaction_typename);
+	if (no == NULL)
+		panic("Default external action handler is not found");
+	if (eaction_id == no->kidx)
+		panic("Wrong eaction_id");
+	EACTION_DEBUG("replace id %u with %u", eaction_id, no->kidx);
+	IPFW_WLOCK(ch);
+	for (i = 0; i < ch->n_rules; i++) {
+		rule = ch->map[i];
+		cmd = ACTION_PTR(rule);
+		if (cmd->opcode != O_EXTERNAL_ACTION)
+			continue;
+		if (cmd->arg1 != eaction_id)
+			continue;
+		cmd->arg1 = no->kidx; /* Set to default id */
+		/*
+		 * XXX: we only bump refcount on default_eaction.
+		 * Refcount on the original object will be just
+		 * ignored on destroy. But on default_eaction it
+		 * will be decremented on rule deletion.
+		 */
+		no->refcnt++;
+		/*
+		 * Since named_object related to this instance will be
+		 * also destroyed, truncate the chain of opcodes to
+		 * remove O_EXTERNAL_INSTANCE opcode.
+		 */
+		if (rule->act_ofs < rule->cmd_len - 1) {
+			EACTION_DEBUG("truncate rule %d", rule->rulenum);
+			rule->cmd_len--;
+		}
+	}
+	IPFW_WUNLOCK(ch);
+}
+
+/*
+ * Initialize external actions framework.
+ * Create object with default eaction handler "drop".
+ */
+int
+ipfw_eaction_init(struct ip_fw_chain *ch, int first)
+{
+	int error;
+
+	error = create_eaction_obj(ch, default_eaction,
+	    default_eaction_typename, NULL);
+	if (error != 0)
+		return (error);
+	IPFW_ADD_OBJ_REWRITER(first, eaction_opcodes);
+	EACTION_DEBUG("External actions support initialized");
+	return (0);
+}
+
+void
+ipfw_eaction_uninit(struct ip_fw_chain *ch, int last)
+{
+	struct namedobj_instance *ni;
+	struct named_object *no;
+
+	ni = CHAIN_TO_SRV(ch);
+
+	IPFW_UH_WLOCK(ch);
+	no = ipfw_objhash_lookup_name_type(ni, 0, IPFW_TLV_EACTION,
+	    default_eaction_typename);
+	if (no != NULL)
+		destroy_eaction_obj(ch, no);
+	IPFW_UH_WUNLOCK(ch);
+	IPFW_DEL_OBJ_REWRITER(last, eaction_opcodes);
+	EACTION_DEBUG("External actions support uninitialized");
+}
+
+/*
+ * Registers external action handler to the global array.
+ * On success it returns eaction id, otherwise - zero.
+ */
+uint16_t
+ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler,
+    const char *name)
+{
+	uint16_t eaction_id;
+
+	eaction_id = 0;
+	if (ipfw_check_object_name_generic(name) == 0) {
+		create_eaction_obj(ch, handler, name, &eaction_id);
+		EACTION_DEBUG("Registered external action '%s' with id %u",
+		    name, eaction_id);
+	}
+	return (eaction_id);
+}
+
+/*
+ * Deregisters external action handler with id eaction_id.
+ */
+int
+ipfw_del_eaction(struct ip_fw_chain *ch, uint16_t eaction_id)
+{
+	struct named_object *no;
+
+	IPFW_UH_WLOCK(ch);
+	no = ipfw_objhash_lookup_kidx(CHAIN_TO_SRV(ch), eaction_id);
+	if (no == NULL || no->etlv != IPFW_TLV_EACTION) {
+		IPFW_UH_WUNLOCK(ch);
+		return (EINVAL);
+	}
+	if (no->refcnt > 1)
+		reset_eaction_obj(ch, eaction_id);
+	EACTION_DEBUG("External action '%s' with id %u unregistered",
+	    no->name, eaction_id);
+	destroy_eaction_obj(ch, no);
+	IPFW_UH_WUNLOCK(ch);
+	return (0);
+}
+
+int
+ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args,
+    ipfw_insn *cmd, int *done)
+{
+
+	return (EACTION_OBJ(ch, cmd)->handler(ch, args, cmd, done));
+}

Modified: head/sys/netpfil/ipfw/ip_fw_private.h
==============================================================================
--- head/sys/netpfil/ipfw/ip_fw_private.h	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/netpfil/ipfw/ip_fw_private.h	Thu Apr 14 22:51:23 2016	(r298016)
@@ -516,9 +516,10 @@ struct ip_fw_bcounter0 {
 #define	IPFW_TABLES_MAX		65536
 #define	IPFW_TABLES_DEFAULT	128
 #define	IPFW_OBJECTS_MAX	65536
-#define	IPFW_OBJECTS_DEFAULT	128
+#define	IPFW_OBJECTS_DEFAULT	1024
 
 #define	CHAIN_TO_SRV(ch)	((ch)->srvmap)
+#define	SRV_OBJECT(ch, idx)	((ch)->srvstate[(idx)])
 
 struct tid_info {
 	uint32_t	set;	/* table set */
@@ -650,9 +651,10 @@ caddr_t ipfw_get_sopt_header(struct sock
 struct namedobj_instance;
 typedef void (objhash_cb_t)(struct namedobj_instance *ni, struct named_object *,
     void *arg);
-typedef uint32_t (objhash_hash_f)(struct namedobj_instance *ni, void *key,
+typedef uint32_t (objhash_hash_f)(struct namedobj_instance *ni, const void *key,
+    uint32_t kopt);
+typedef int (objhash_cmp_f)(struct named_object *no, const void *key,
     uint32_t kopt);
-typedef int (objhash_cmp_f)(struct named_object *no, void *key, uint32_t kopt);
 struct namedobj_instance *ipfw_objhash_create(uint32_t items);
 void ipfw_objhash_destroy(struct namedobj_instance *);
 void ipfw_objhash_bitmap_alloc(uint32_t items, void **idx, int *pblocks);
@@ -665,7 +667,7 @@ void ipfw_objhash_set_hashf(struct named
 struct named_object *ipfw_objhash_lookup_name(struct namedobj_instance *ni,
     uint32_t set, char *name);
 struct named_object *ipfw_objhash_lookup_name_type(struct namedobj_instance *ni,
-    uint32_t set, uint32_t type, char *name);
+    uint32_t set, uint32_t type, const char *name);
 struct named_object *ipfw_objhash_lookup_kidx(struct namedobj_instance *ni,
     uint16_t idx);
 int ipfw_objhash_same_name(struct namedobj_instance *ni, struct named_object *a,
@@ -679,6 +681,8 @@ int ipfw_objhash_free_idx(struct namedob
 int ipfw_objhash_alloc_idx(void *n, uint16_t *pidx);
 void ipfw_objhash_set_funcs(struct namedobj_instance *ni,
     objhash_hash_f *hash_f, objhash_cmp_f *cmp_f);
+int ipfw_objhash_find_type(struct namedobj_instance *ni, struct tid_info *ti,
+    uint32_t etlv, struct named_object **pno);
 void ipfw_export_obj_ntlv(struct named_object *no, ipfw_obj_ntlv *ntlv);
 void ipfw_init_obj_rewriter(void);
 void ipfw_destroy_obj_rewriter(void);
@@ -693,6 +697,18 @@ void ipfw_init_srv(struct ip_fw_chain *c
 void ipfw_destroy_srv(struct ip_fw_chain *ch);
 int ipfw_check_object_name_generic(const char *name);
 
+/* In ip_fw_eaction.c */
+typedef int (ipfw_eaction_t)(struct ip_fw_chain *ch, struct ip_fw_args *args,
+    ipfw_insn *cmd, int *done);
+int ipfw_eaction_init(struct ip_fw_chain *ch, int first);
+void ipfw_eaction_uninit(struct ip_fw_chain *ch, int last);
+
+uint16_t ipfw_add_eaction(struct ip_fw_chain *ch, ipfw_eaction_t handler,
+    const char *name);
+int ipfw_del_eaction(struct ip_fw_chain *ch, uint16_t eaction_id);
+int ipfw_run_eaction(struct ip_fw_chain *ch, struct ip_fw_args *args,
+    ipfw_insn *cmd, int *done);
+
 /* In ip_fw_table.c */
 struct table_info;
 

Modified: head/sys/netpfil/ipfw/ip_fw_sockopt.c
==============================================================================
--- head/sys/netpfil/ipfw/ip_fw_sockopt.c	Thu Apr 14 22:38:13 2016	(r298015)
+++ head/sys/netpfil/ipfw/ip_fw_sockopt.c	Thu Apr 14 22:51:23 2016	(r298016)
@@ -100,10 +100,11 @@ struct namedobj_instance {
 };
 #define	BLOCK_ITEMS	(8 * sizeof(u_long))	/* Number of items for ffsl() */
 
-static uint32_t objhash_hash_name(struct namedobj_instance *ni, void *key,
-    uint32_t kopt);
+static uint32_t objhash_hash_name(struct namedobj_instance *ni,
+    const void *key, uint32_t kopt);
 static uint32_t objhash_hash_idx(struct namedobj_instance *ni, uint32_t val);
-static int objhash_cmp_name(struct named_object *no, void *name, uint32_t set);
+static int objhash_cmp_name(struct named_object *no, const void *name,
+    uint32_t set);
 
 MALLOC_DEFINE(M_IPFW, "IpFw/IpAcct", "IpFw/IpAcct chain's");
 
@@ -1492,6 +1493,35 @@ check_ipfw_rule_body(ipfw_insn *cmd, int
 				goto bad_size;
 			break;
 
+		case O_EXTERNAL_ACTION:
+			if (cmd->arg1 == 0 ||
+			    cmdlen != F_INSN_SIZE(ipfw_insn)) {
+				printf("ipfw: invalid external "
+				    "action opcode\n");
+				return (EINVAL);
+			}
+			ci->object_opcodes++;
+			/* Do we have O_EXTERNAL_INSTANCE opcode? */
+			if (l != cmdlen) {
+				l -= cmdlen;
+				cmd += cmdlen;
+				cmdlen = F_LEN(cmd);
+				if (cmd->opcode != O_EXTERNAL_INSTANCE) {
+					printf("ipfw: invalid opcode "
+					    "next to external action %u\n",
+					    cmd->opcode);
+					return (EINVAL);
+				}
+				if (cmd->arg1 == 0 ||
+				    cmdlen != F_INSN_SIZE(ipfw_insn)) {
+					printf("ipfw: invalid external "
+					    "action instance opcode\n");
+					return (EINVAL);
+				}
+				ci->object_opcodes++;
+			}
+			goto check_action;
+
 		case O_FIB:
 			if (cmdlen != F_INSN_SIZE(ipfw_insn))
 				goto bad_size;
@@ -4008,17 +4038,17 @@ ipfw_objhash_set_funcs(struct namedobj_i
 }
 
 static uint32_t
-objhash_hash_name(struct namedobj_instance *ni, void *name, uint32_t set)
+objhash_hash_name(struct namedobj_instance *ni, const void *name, uint32_t set)
 {
 
-	return (fnv_32_str((char *)name, FNV1_32_INIT));
+	return (fnv_32_str((const char *)name, FNV1_32_INIT));
 }
 
 static int
-objhash_cmp_name(struct named_object *no, void *name, uint32_t set)
+objhash_cmp_name(struct named_object *no, const void *name, uint32_t set)
 {
 
-	if ((strcmp(no->name, (char *)name) == 0) && (no->set == set))
+	if ((strcmp(no->name, (const char *)name) == 0) && (no->set == set))
 		return (0);
 
 	return (1);
@@ -4051,11 +4081,90 @@ ipfw_objhash_lookup_name(struct namedobj
 }
 
 /*
+ * Find named object by @uid.
+ * Check @tlvs for valid data inside.
+ *
+ * Returns pointer to found TLV or NULL.
+ */
+static ipfw_obj_ntlv *
+find_name_tlv_type(void *tlvs, int len, uint16_t uidx, uint32_t etlv)
+{
+	ipfw_obj_ntlv *ntlv;
+	uintptr_t pa, pe;
+	int l;
+
+	pa = (uintptr_t)tlvs;
+	pe = pa + len;
+	l = 0;
+	for (; pa < pe; pa += l) {
+		ntlv = (ipfw_obj_ntlv *)pa;
+		l = ntlv->head.length;
+
+		if (l != sizeof(*ntlv))
+			return (NULL);
+
+		if (ntlv->idx != uidx)
+			continue;
+		/*
+		 * When userland has specified zero TLV type, do
+		 * not compare it with eltv. In some cases userland
+		 * doesn't know what type should it have. Use only
+		 * uidx and name for search named_object.
+		 */
+		if (ntlv->head.type != 0 &&
+		    ntlv->head.type != (uint16_t)etlv)
+			continue;
+
+		if (ipfw_check_object_name_generic(ntlv->name) != 0)
+			return (NULL);
+
+		return (ntlv);
+	}
+
+	return (NULL);
+}
+
+/*
+ * Finds object config based on either legacy index
+ * or name in ntlv.
+ * Note @ti structure contains unchecked data from userland.

*** DIFF OUTPUT TRUNCATED AT 1000 LINES ***



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