git: 4bf98559d9d6 - main - pf: make contents of struct pfsync_state configurable

From: Kristof Provost <kp_at_FreeBSD.org>
Date: Tue, 30 May 2023 12:29:24 UTC
The branch main has been updated by kp:

URL: https://cgit.FreeBSD.org/src/commit/?id=4bf98559d9d6fa7c3571d26ed6f2b18823e3a30b

commit 4bf98559d9d6fa7c3571d26ed6f2b18823e3a30b
Author:     Kajetan Staszkiewicz <vegeta@tuxpowered.net>
AuthorDate: 2023-05-29 13:47:23 +0000
Commit:     Kristof Provost <kp@FreeBSD.org>
CommitDate: 2023-05-30 12:28:56 +0000

    pf: make contents of struct pfsync_state configurable
    
    Make struct pfsync_state contents configurable by sending out new
    versions of the structure in separate subheader actions. Both old and
    new version of struct pfsync_state can be understood, so replication of
    states from a system running an older kernel is possible. The version
    being sent out is configured using ifconfig pfsync0 … version XXXX. The
    version is an user-friendly string - 1301 stands for FreeBSD 13.1 (I
    have checked synchronization against a host running 13.1), 1400 stands
    for 14.0.
    
    A host running an older kernel will just ignore the messages and count
    them as "packets discarded for bad action".
    
    Reviewed by:    kp
    Sponsored by:   InnoGames GmbH
    Differential Revision:  https://reviews.freebsd.org/D39392
---
 contrib/tcpdump/print-pfsync.c |  98 +++++----
 sbin/ifconfig/ifpfsync.c       |  28 ++-
 sys/net/if_pfsync.h            |  19 +-
 sys/net/pfvar.h                |  68 ++++++-
 sys/netpfil/pf/if_pfsync.c     | 445 ++++++++++++++++++++++++++++-------------
 sys/netpfil/pf/pf.c            | 193 +++++++++++++-----
 sys/netpfil/pf/pf_ioctl.c      | 132 +++++++-----
 sys/netpfil/pf/pfsync_nv.c     |   1 +
 usr.bin/netstat/if.c           |  12 +-
 9 files changed, 688 insertions(+), 308 deletions(-)

diff --git a/contrib/tcpdump/print-pfsync.c b/contrib/tcpdump/print-pfsync.c
index dc1cd039f5b0..ee13cade1b14 100644
--- a/contrib/tcpdump/print-pfsync.c
+++ b/contrib/tcpdump/print-pfsync.c
@@ -55,7 +55,7 @@ static void	pfsync_print(netdissect_options *, struct pfsync_header *,
 static void	print_src_dst(netdissect_options *,
 		    const struct pfsync_state_peer *,
 		    const struct pfsync_state_peer *, uint8_t);
-static void	print_state(netdissect_options *, struct pfsync_state *);
+static void	print_state(netdissect_options *, union pfsync_state_union *, int);
 
 u_int
 pfsync_if_print(netdissect_options *ndo, const struct pcap_pkthdr *h,
@@ -100,7 +100,8 @@ struct pfsync_actions {
 };
 
 static void	pfsync_print_clr(netdissect_options *, const void *);
-static void	pfsync_print_state(netdissect_options *, const void *);
+static void	pfsync_print_state_1301(netdissect_options *, const void *);
+static void	pfsync_print_state_1400(netdissect_options *, const void *);
 static void	pfsync_print_ins_ack(netdissect_options *, const void *);
 static void	pfsync_print_upd_c(netdissect_options *, const void *);
 static void	pfsync_print_upd_req(netdissect_options *, const void *);
@@ -110,14 +111,16 @@ static void	pfsync_print_tdb(netdissect_options *, const void *);
 
 struct pfsync_actions actions[] = {
 	{ "clear all", sizeof(struct pfsync_clr),	pfsync_print_clr },
-	{ "insert", sizeof(struct pfsync_state),	pfsync_print_state },
+	{ "insert 13.1", sizeof(struct pfsync_state_1301),
+							pfsync_print_state_1301 },
 	{ "insert ack", sizeof(struct pfsync_ins_ack),	pfsync_print_ins_ack },
-	{ "update", sizeof(struct pfsync_ins_ack),	pfsync_print_state },
+	{ "update 13.1", sizeof(struct pfsync_state_1301),
+							pfsync_print_state_1301 },
 	{ "update compressed", sizeof(struct pfsync_upd_c),
 							pfsync_print_upd_c },
 	{ "request uncompressed", sizeof(struct pfsync_upd_req),
 							pfsync_print_upd_req },
-	{ "delete", sizeof(struct pfsync_state),	pfsync_print_state },
+	{ "delete", sizeof(struct pfsync_state_1301),	pfsync_print_state_1301 },
 	{ "delete compressed", sizeof(struct pfsync_del_c),
 							pfsync_print_del_c },
 	{ "frag insert", 0,				NULL },
@@ -126,6 +129,8 @@ struct pfsync_actions actions[] = {
 							pfsync_print_bus },
 	{ "tdb", 0,					pfsync_print_tdb },
 	{ "eof", 0,					NULL },
+	{ "insert", sizeof(struct pfsync_state_1400),	pfsync_print_state_1400 },
+	{ "update", sizeof(struct pfsync_state_1400),	pfsync_print_state_1400 },
 };
 
 static void
@@ -212,12 +217,21 @@ pfsync_print_clr(netdissect_options *ndo, const void *bp)
 }
 
 static void
-pfsync_print_state(netdissect_options *ndo, const void *bp)
+pfsync_print_state_1301(netdissect_options *ndo, const void *bp)
 {
-	struct pfsync_state *st = (struct pfsync_state *)bp;
+	struct pfsync_state_1301 *st = (struct pfsync_state_1301 *)bp;
 
 	safeputchar(ndo, '\n');
-	print_state(ndo, st);
+	print_state(ndo, (union pfsync_state_union *)st, PFSYNC_MSG_VERSION_1301);
+}
+
+static void
+pfsync_print_state_1400(netdissect_options *ndo, const void *bp)
+{
+	struct pfsync_state_1301 *st = (struct pfsync_state_1301 *)bp;
+
+	safeputchar(ndo, '\n');
+	print_state(ndo, (union pfsync_state_union *)st, PFSYNC_MSG_VERSION_1400);
 }
 
 static void
@@ -374,56 +388,56 @@ print_src_dst(netdissect_options *ndo, const struct pfsync_state_peer *src,
 }
 
 static void
-print_state(netdissect_options *ndo, struct pfsync_state *s)
+print_state(netdissect_options *ndo, union pfsync_state_union *s, int version)
 {
 	struct pfsync_state_peer *src, *dst;
 	struct pfsync_state_key *sk, *nk;
 	int min, sec;
 
-	if (s->direction == PF_OUT) {
-		src = &s->src;
-		dst = &s->dst;
-		sk = &s->key[PF_SK_STACK];
-		nk = &s->key[PF_SK_WIRE];
-		if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6)
+	if (s->pfs_1301.direction == PF_OUT) {
+		src = &s->pfs_1301.src;
+		dst = &s->pfs_1301.dst;
+		sk = &s->pfs_1301.key[PF_SK_STACK];
+		nk = &s->pfs_1301.key[PF_SK_WIRE];
+		if (s->pfs_1301.proto == IPPROTO_ICMP || s->pfs_1301.proto == IPPROTO_ICMPV6)
 			sk->port[0] = nk->port[0];
 	} else {
-		src = &s->dst;
-		dst = &s->src;
-		sk = &s->key[PF_SK_WIRE];
-		nk = &s->key[PF_SK_STACK];
-		if (s->proto == IPPROTO_ICMP || s->proto == IPPROTO_ICMPV6)
+		src = &s->pfs_1301.dst;
+		dst = &s->pfs_1301.src;
+		sk = &s->pfs_1301.key[PF_SK_WIRE];
+		nk = &s->pfs_1301.key[PF_SK_STACK];
+		if (s->pfs_1301.proto == IPPROTO_ICMP || s->pfs_1301.proto == IPPROTO_ICMPV6)
 			sk->port[1] = nk->port[1];
 	}
-	ND_PRINT((ndo, "\t%s ", s->ifname));
-	ND_PRINT((ndo, "proto %u ", s->proto));
+	ND_PRINT((ndo, "\t%s ", s->pfs_1301.ifname));
+	ND_PRINT((ndo, "proto %u ", s->pfs_1301.proto));
 
-	print_host(ndo, &nk->addr[1], nk->port[1], s->af, NULL);
-	if (PF_ANEQ(&nk->addr[1], &sk->addr[1], s->af) ||
+	print_host(ndo, &nk->addr[1], nk->port[1], s->pfs_1301.af, NULL);
+	if (PF_ANEQ(&nk->addr[1], &sk->addr[1], s->pfs_1301.af) ||
 	    nk->port[1] != sk->port[1]) {
 		ND_PRINT((ndo, " ("));
-		print_host(ndo, &sk->addr[1], sk->port[1], s->af, NULL);
+		print_host(ndo, &sk->addr[1], sk->port[1], s->pfs_1301.af, NULL);
 		ND_PRINT((ndo, ")"));
 	}
-	if (s->direction == PF_OUT)
+	if (s->pfs_1301.direction == PF_OUT)
 		ND_PRINT((ndo, " -> "));
 	else
 		ND_PRINT((ndo, " <- "));
-	print_host(ndo, &nk->addr[0], nk->port[0], s->af, NULL);
-	if (PF_ANEQ(&nk->addr[0], &sk->addr[0], s->af) ||
+	print_host(ndo, &nk->addr[0], nk->port[0], s->pfs_1301.af, NULL);
+	if (PF_ANEQ(&nk->addr[0], &sk->addr[0], s->pfs_1301.af) ||
 	    nk->port[0] != sk->port[0]) {
 		ND_PRINT((ndo, " ("));
-		print_host(ndo, &sk->addr[0], sk->port[0], s->af, NULL);
+		print_host(ndo, &sk->addr[0], sk->port[0], s->pfs_1301.af, NULL);
 		ND_PRINT((ndo, ")"));
 	}
 
-	print_src_dst(ndo, src, dst, s->proto);
+	print_src_dst(ndo, src, dst, s->pfs_1301.proto);
 
 	if (ndo->ndo_vflag > 1) {
 		uint64_t packets[2];
 		uint64_t bytes[2];
-		uint32_t creation = ntohl(s->creation);
-		uint32_t expire = ntohl(s->expire);
+		uint32_t creation = ntohl(s->pfs_1301.creation);
+		uint32_t expire = ntohl(s->pfs_1301.expire);
 
 		sec = creation % 60;
 		creation /= 60;
@@ -436,23 +450,23 @@ print_state(netdissect_options *ndo, struct pfsync_state *s)
 		expire /= 60;
 		ND_PRINT((ndo, ", expires in %.2u:%.2u:%.2u", expire, min, sec));
 
-		bcopy(s->packets[0], &packets[0], sizeof(uint64_t));
-		bcopy(s->packets[1], &packets[1], sizeof(uint64_t));
-		bcopy(s->bytes[0], &bytes[0], sizeof(uint64_t));
-		bcopy(s->bytes[1], &bytes[1], sizeof(uint64_t));
+		bcopy(s->pfs_1301.packets[0], &packets[0], sizeof(uint64_t));
+		bcopy(s->pfs_1301.packets[1], &packets[1], sizeof(uint64_t));
+		bcopy(s->pfs_1301.bytes[0], &bytes[0], sizeof(uint64_t));
+		bcopy(s->pfs_1301.bytes[1], &bytes[1], sizeof(uint64_t));
 		ND_PRINT((ndo, ", %ju:%ju pkts, %ju:%ju bytes",
 		    be64toh(packets[0]), be64toh(packets[1]),
 		    be64toh(bytes[0]), be64toh(bytes[1])));
-		if (s->anchor != ntohl(-1))
-			ND_PRINT((ndo, ", anchor %u", ntohl(s->anchor)));
-		if (s->rule != ntohl(-1))
-			ND_PRINT((ndo, ", rule %u", ntohl(s->rule)));
+		if (s->pfs_1301.anchor != ntohl(-1))
+			ND_PRINT((ndo, ", anchor %u", ntohl(s->pfs_1301.anchor)));
+		if (s->pfs_1301.rule != ntohl(-1))
+			ND_PRINT((ndo, ", rule %u", ntohl(s->pfs_1301.rule)));
 	}
 	if (ndo->ndo_vflag > 1) {
 		uint64_t id;
 
-		bcopy(&s->id, &id, sizeof(uint64_t));
+		bcopy(&s->pfs_1301.id, &id, sizeof(uint64_t));
 		ND_PRINT((ndo, "\n\tid: %016jx creatorid: %08x",
-		    (uintmax_t )be64toh(id), ntohl(s->creatorid)));
+		    (uintmax_t )be64toh(id), ntohl(s->pfs_1301.creatorid)));
 	}
 }
diff --git a/sbin/ifconfig/ifpfsync.c b/sbin/ifconfig/ifpfsync.c
index 8fd15962c2d0..de2a2445afb4 100644
--- a/sbin/ifconfig/ifpfsync.c
+++ b/sbin/ifconfig/ifpfsync.c
@@ -309,6 +309,27 @@ setpfsync_defer(if_ctx *ctx, const char *val, int d)
 	nvlist_destroy(nvl);
 }
 
+static void
+setpfsync_version(if_ctx *ctx, const char *val, int dummy __unused)
+{
+	int version;
+	nvlist_t *nvl = nvlist_create(0);
+
+	/* Don't verify, kernel knows which versions are supported.*/
+	version = atoi(val);
+
+	if (pfsync_do_ioctl(ctx->io_s, SIOCGETPFSYNCNV, &nvl) == -1)
+		err(1, "SIOCGETPFSYNCNV");
+
+	nvlist_free_number(nvl, "version");
+	nvlist_add_number(nvl, "version", version);
+
+	if (pfsync_do_ioctl(ctx->io_s, SIOCSETPFSYNCNV, &nvl) == -1)
+		err(1, "SIOCSETPFSYNCNV");
+
+	nvlist_destroy(nvl);
+}
+
 static void
 pfsync_status(if_ctx *ctx)
 {
@@ -318,6 +339,7 @@ pfsync_status(if_ctx *ctx)
 	struct sockaddr_storage syncpeer;
 	int maxupdates = 0;
 	int flags = 0;
+	int version;
 	int error;
 
 	nvl = nvlist_create(0);
@@ -333,6 +355,8 @@ pfsync_status(if_ctx *ctx)
 		    IFNAMSIZ);
 	if (nvlist_exists_number(nvl, "maxupdates"))
 		maxupdates = nvlist_get_number(nvl, "maxupdates");
+	if (nvlist_exists_number(nvl, "version"))
+		version = nvlist_get_number(nvl, "version");
 	if (nvlist_exists_number(nvl, "flags"))
 		flags = nvlist_get_number(nvl, "flags");
 	if (nvlist_exists_nvlist(nvl, "syncpeer")) {
@@ -363,7 +387,8 @@ pfsync_status(if_ctx *ctx)
 	}
 
 	printf("maxupd: %d ", maxupdates);
-	printf("defer: %s\n", (flags & PFSYNCF_DEFER) ? "on" : "off");
+	printf("defer: %s ", (flags & PFSYNCF_DEFER) ? "on" : "off");
+	printf("version: %d\n", version);
 	printf("\tsyncok: %d\n", (flags & PFSYNCF_OK) ? 1 : 0);
 }
 
@@ -377,6 +402,7 @@ static struct cmd pfsync_cmds[] = {
 	DEF_CMD_ARG("maxupd",		setpfsync_maxupd),
 	DEF_CMD("defer",	1,	setpfsync_defer),
 	DEF_CMD("-defer",	0,	setpfsync_defer),
+	DEF_CMD_ARG("version",		setpfsync_version),
 };
 static struct afswtch af_pfsync = {
 	.af_name	= "af_pfsync",
diff --git a/sys/net/if_pfsync.h b/sys/net/if_pfsync.h
index a13e26fd3bdf..ef5c26285781 100644
--- a/sys/net/if_pfsync.h
+++ b/sys/net/if_pfsync.h
@@ -59,10 +59,18 @@
 #define	PFSYNC_VERSION		5
 #define	PFSYNC_DFLTTL		255
 
+enum pfsync_msg_versions {
+	PFSYNC_MSG_VERSION_UNSPECIFIED = 0,
+	PFSYNC_MSG_VERSION_1301 = 1301,
+	PFSYNC_MSG_VERSION_1400 = 1400,
+};
+
+#define PFSYNC_MSG_VERSION_DEFAULT PFSYNC_MSG_VERSION_1400
+
 #define	PFSYNC_ACT_CLR		0	/* clear all states */
-#define	PFSYNC_ACT_INS		1	/* insert state */
+#define	PFSYNC_ACT_INS_1301	1	/* insert state */
 #define	PFSYNC_ACT_INS_ACK	2	/* ack of inserted state */
-#define	PFSYNC_ACT_UPD		3	/* update state */
+#define	PFSYNC_ACT_UPD_1301	3	/* update state */
 #define	PFSYNC_ACT_UPD_C	4	/* "compressed" update state */
 #define	PFSYNC_ACT_UPD_REQ	5	/* request "uncompressed" state */
 #define	PFSYNC_ACT_DEL		6	/* delete state */
@@ -72,7 +80,9 @@
 #define	PFSYNC_ACT_BUS		10	/* bulk update status */
 #define	PFSYNC_ACT_TDB		11	/* TDB replay counter update */
 #define	PFSYNC_ACT_EOF		12	/* end of frame */
-#define	PFSYNC_ACT_MAX		13
+#define PFSYNC_ACT_INS_1400	13	/* insert state */
+#define PFSYNC_ACT_UPD_1400	14	/* update state */
+#define	PFSYNC_ACT_MAX		15
 
 /*
  * A pfsync frame is built from a header followed by several sections which
@@ -251,6 +261,7 @@ struct pfsync_kstatus {
 	char		 	syncdev[IFNAMSIZ];
 	struct sockaddr_storage	syncpeer;
 	int		 	maxupdates;
+	int			version;
 	int		 	flags;
 };
 
@@ -269,13 +280,13 @@ struct pfsyncioc_nv {
 
 /*
  * this shows where a pf state is with respect to the syncing.
+ * pf_kstate->sync_state
  */
 #define	PFSYNC_S_INS	0x00
 #define	PFSYNC_S_IACK	0x01
 #define	PFSYNC_S_UPD	0x02
 #define	PFSYNC_S_UPD_C	0x03
 #define	PFSYNC_S_DEL_C	0x04
-#define	PFSYNC_S_COUNT	0x05
 
 #define	PFSYNC_S_DEFER	0xfe
 #define	PFSYNC_S_NONE	0xff
diff --git a/sys/net/pfvar.h b/sys/net/pfvar.h
index 4bdfa22b58ab..c5923bc9abdf 100644
--- a/sys/net/pfvar.h
+++ b/sys/net/pfvar.h
@@ -641,6 +641,7 @@ struct pf_rule_actions {
 	uint16_t	 dnpipe;
 	uint16_t	 dnrpipe;	/* Reverse direction pipe */
 	uint32_t	 flags;
+	uint8_t		 set_prio[2];
 };
 
 union pf_keth_rule_ptr {
@@ -1057,12 +1058,14 @@ struct pf_kstate {
 	u_int8_t		 min_ttl;
 	u_int8_t		 set_tos;
 	u_int16_t		 max_mss;
+	u_int8_t		 rt;
+	u_int8_t		 set_prio[2];
 };
 
 /*
- * Size <= fits 12 objects per page on LP64. Try to not grow the struct beyond that.
+ * Size <= fits 11 objects per page on LP64. Try to not grow the struct beyond that.
  */
-_Static_assert(sizeof(struct pf_kstate) <= 336, "pf_kstate size crosses 336 bytes");
+_Static_assert(sizeof(struct pf_kstate) <= 368, "pf_kstate size crosses 368 bytes");
 #endif
 
 /*
@@ -1094,7 +1097,34 @@ struct pfsync_state_key {
 	u_int16_t	 port[2];
 };
 
-struct pfsync_state {
+struct pfsync_state_1301 {
+	u_int64_t	 id;
+	char		 ifname[IFNAMSIZ];
+	struct pfsync_state_key	key[2];
+	struct pfsync_state_peer src;
+	struct pfsync_state_peer dst;
+	struct pf_addr	 rt_addr;
+	u_int32_t	 rule;
+	u_int32_t	 anchor;
+	u_int32_t	 nat_rule;
+	u_int32_t	 creation;
+	u_int32_t	 expire;
+	u_int32_t	 packets[2][2];
+	u_int32_t	 bytes[2][2];
+	u_int32_t	 creatorid;
+	sa_family_t	 af;
+	u_int8_t	 proto;
+	u_int8_t	 direction;
+	u_int8_t	 __spare[2];
+	u_int8_t	 log;
+	u_int8_t	 state_flags;
+	u_int8_t	 timeout;
+	u_int8_t	 sync_flags;
+	u_int8_t	 updates;
+} __packed;
+
+struct pfsync_state_1400 {
+	/* The beginning of the struct is compatible with previous versions */
 	u_int64_t	 id;
 	char		 ifname[IFNAMSIZ];
 	struct pfsync_state_key	key[2];
@@ -1114,15 +1144,33 @@ struct pfsync_state {
 	u_int8_t	 direction;
 	u_int16_t	 state_flags;
 	u_int8_t	 log;
-	u_int8_t	 state_flags_compat;
+	u_int8_t	 __spare;
 	u_int8_t	 timeout;
 	u_int8_t	 sync_flags;
 	u_int8_t	 updates;
+	/* The rest is not */
+	u_int16_t	 qid;
+	u_int16_t	 pqid;
+	u_int16_t	 dnpipe;
+	u_int16_t	 dnrpipe;
+	int32_t		 rtableid;
+	u_int8_t	 min_ttl;
+	u_int8_t	 set_tos;
+	u_int16_t	 max_mss;
+	u_int8_t	 set_prio[2];
+	u_int8_t	 rt;
+	char		 rt_ifname[IFNAMSIZ];
+
+} __packed;
+
+union pfsync_state_union {
+	struct pfsync_state_1301 pfs_1301;
+	struct pfsync_state_1400 pfs_1400;
 } __packed;
 
 #ifdef _KERNEL
 /* pfsync */
-typedef int		pfsync_state_import_t(struct pfsync_state *, int);
+typedef int		pfsync_state_import_t(union pfsync_state_union *, int, int);
 typedef	void		pfsync_insert_state_t(struct pf_kstate *);
 typedef	void		pfsync_update_state_t(struct pf_kstate *);
 typedef	void		pfsync_delete_state_t(struct pf_kstate *);
@@ -1144,8 +1192,8 @@ VNET_DECLARE(pfsync_defer_t *, pfsync_defer_ptr);
 #define V_pfsync_defer_ptr		VNET(pfsync_defer_ptr)
 extern pfsync_detach_ifnet_t	*pfsync_detach_ifnet_ptr;
 
-void			pfsync_state_export(struct pfsync_state *,
-			    struct pf_kstate *);
+void			pfsync_state_export(union pfsync_state_union *,
+			    struct pf_kstate *, int);
 void			pf_state_export(struct pf_state_export *,
 			    struct pf_kstate *);
 
@@ -1665,7 +1713,7 @@ struct pfioc_natlook {
 };
 
 struct pfioc_state {
-	struct pfsync_state	state;
+	struct pfsync_state_1301	state;
 };
 
 struct pfioc_src_node_kill {
@@ -1704,8 +1752,8 @@ struct pfioc_state_kill {
 struct pfioc_states {
 	int	ps_len;
 	union {
-		void			*ps_buf;
-		struct pfsync_state	*ps_states;
+		void				*ps_buf;
+		struct pfsync_state_1301	*ps_states;
 	};
 };
 
diff --git a/sys/netpfil/pf/if_pfsync.c b/sys/netpfil/pf/if_pfsync.c
index f53479283ecd..67f986e6abd2 100644
--- a/sys/netpfil/pf/if_pfsync.c
+++ b/sys/netpfil/pf/if_pfsync.c
@@ -122,23 +122,23 @@ union inet_template {
 
 static int	pfsync_upd_tcp(struct pf_kstate *, struct pfsync_state_peer *,
 		    struct pfsync_state_peer *);
-static int	pfsync_in_clr(struct mbuf *, int, int, int);
-static int	pfsync_in_ins(struct mbuf *, int, int, int);
-static int	pfsync_in_iack(struct mbuf *, int, int, int);
-static int	pfsync_in_upd(struct mbuf *, int, int, int);
-static int	pfsync_in_upd_c(struct mbuf *, int, int, int);
-static int	pfsync_in_ureq(struct mbuf *, int, int, int);
-static int	pfsync_in_del_c(struct mbuf *, int, int, int);
-static int	pfsync_in_bus(struct mbuf *, int, int, int);
-static int	pfsync_in_tdb(struct mbuf *, int, int, int);
-static int	pfsync_in_eof(struct mbuf *, int, int, int);
-static int	pfsync_in_error(struct mbuf *, int, int, int);
-
-static int (*pfsync_acts[])(struct mbuf *, int, int, int) = {
+static int	pfsync_in_clr(struct mbuf *, int, int, int, int);
+static int	pfsync_in_ins(struct mbuf *, int, int, int, int);
+static int	pfsync_in_iack(struct mbuf *, int, int, int, int);
+static int	pfsync_in_upd(struct mbuf *, int, int, int, int);
+static int	pfsync_in_upd_c(struct mbuf *, int, int, int, int);
+static int	pfsync_in_ureq(struct mbuf *, int, int, int, int);
+static int	pfsync_in_del_c(struct mbuf *, int, int, int, int);
+static int	pfsync_in_bus(struct mbuf *, int, int, int, int);
+static int	pfsync_in_tdb(struct mbuf *, int, int, int, int);
+static int	pfsync_in_eof(struct mbuf *, int, int, int, int);
+static int	pfsync_in_error(struct mbuf *, int, int, int, int);
+
+static int (*pfsync_acts[])(struct mbuf *, int, int, int, int) = {
 	pfsync_in_clr,			/* PFSYNC_ACT_CLR */
-	pfsync_in_ins,			/* PFSYNC_ACT_INS */
+	pfsync_in_ins,			/* PFSYNC_ACT_INS_1301 */
 	pfsync_in_iack,			/* PFSYNC_ACT_INS_ACK */
-	pfsync_in_upd,			/* PFSYNC_ACT_UPD */
+	pfsync_in_upd,			/* PFSYNC_ACT_UPD_1301 */
 	pfsync_in_upd_c,		/* PFSYNC_ACT_UPD_C */
 	pfsync_in_ureq,			/* PFSYNC_ACT_UPD_REQ */
 	pfsync_in_error,		/* PFSYNC_ACT_DEL */
@@ -147,7 +147,9 @@ static int (*pfsync_acts[])(struct mbuf *, int, int, int) = {
 	pfsync_in_error,		/* PFSYNC_ACT_DEL_F */
 	pfsync_in_bus,			/* PFSYNC_ACT_BUS */
 	pfsync_in_tdb,			/* PFSYNC_ACT_TDB */
-	pfsync_in_eof			/* PFSYNC_ACT_EOF */
+	pfsync_in_eof,			/* PFSYNC_ACT_EOF */
+	pfsync_in_ins,			/* PFSYNC_ACT_INS_1400 */
+	pfsync_in_upd,			/* PFSYNC_ACT_UPD_1400 */
 };
 
 struct pfsync_q {
@@ -156,21 +158,51 @@ struct pfsync_q {
 	u_int8_t	action;
 };
 
-/* we have one of these for every PFSYNC_S_ */
-static void	pfsync_out_state(struct pf_kstate *, void *);
+/* We have the following sync queues */
+enum pfsync_q_id {
+	PFSYNC_Q_INS_1301,
+	PFSYNC_Q_INS_1400,
+	PFSYNC_Q_IACK,
+	PFSYNC_Q_UPD_1301,
+	PFSYNC_Q_UPD_1400,
+	PFSYNC_Q_UPD_C,
+	PFSYNC_Q_DEL_C,
+	PFSYNC_Q_COUNT,
+};
+
+/* Functions for building messages for given queue */
+static void	pfsync_out_state_1301(struct pf_kstate *, void *);
+static void	pfsync_out_state_1400(struct pf_kstate *, void *);
 static void	pfsync_out_iack(struct pf_kstate *, void *);
 static void	pfsync_out_upd_c(struct pf_kstate *, void *);
 static void	pfsync_out_del_c(struct pf_kstate *, void *);
 
+/* Attach those functions to queue */
 static struct pfsync_q pfsync_qs[] = {
-	{ pfsync_out_state, sizeof(struct pfsync_state),   PFSYNC_ACT_INS },
-	{ pfsync_out_iack,  sizeof(struct pfsync_ins_ack), PFSYNC_ACT_INS_ACK },
-	{ pfsync_out_state, sizeof(struct pfsync_state),   PFSYNC_ACT_UPD },
-	{ pfsync_out_upd_c, sizeof(struct pfsync_upd_c),   PFSYNC_ACT_UPD_C },
-	{ pfsync_out_del_c, sizeof(struct pfsync_del_c),   PFSYNC_ACT_DEL_C }
+	{ pfsync_out_state_1301, sizeof(struct pfsync_state_1301), PFSYNC_ACT_INS_1301 },
+	{ pfsync_out_state_1400, sizeof(struct pfsync_state_1400), PFSYNC_ACT_INS_1400 },
+	{ pfsync_out_iack,       sizeof(struct pfsync_ins_ack),    PFSYNC_ACT_INS_ACK },
+	{ pfsync_out_state_1301, sizeof(struct pfsync_state_1301), PFSYNC_ACT_UPD_1301 },
+	{ pfsync_out_state_1400, sizeof(struct pfsync_state_1400), PFSYNC_ACT_UPD_1400 },
+	{ pfsync_out_upd_c,      sizeof(struct pfsync_upd_c),      PFSYNC_ACT_UPD_C },
+	{ pfsync_out_del_c,      sizeof(struct pfsync_del_c),      PFSYNC_ACT_DEL_C }
+};
+
+/* Map queue to pf_kstate->sync_state */
+static u_int8_t pfsync_qid_sstate[] = {
+	PFSYNC_S_INS,   /* PFSYNC_Q_INS_1301 */
+	PFSYNC_S_INS,   /* PFSYNC_Q_INS_1400 */
+	PFSYNC_S_IACK,  /* PFSYNC_Q_IACK */
+	PFSYNC_S_UPD,   /* PFSYNC_Q_UPD_1301 */
+	PFSYNC_S_UPD,   /* PFSYNC_Q_UPD_1400 */
+	PFSYNC_S_UPD_C, /* PFSYNC_Q_UPD_C */
+	PFSYNC_S_DEL_C, /* PFSYNC_Q_DEL_C */
 };
 
-static void	pfsync_q_ins(struct pf_kstate *, int, bool);
+/* Map pf_kstate->sync_state to queue */
+static enum pfsync_q_id pfsync_sstate_to_qid(u_int8_t);
+
+static void	pfsync_q_ins(struct pf_kstate *, int sync_state, bool);
 static void	pfsync_q_del(struct pf_kstate *, bool, struct pfsync_bucket *);
 
 static void	pfsync_update_state(struct pf_kstate *);
@@ -200,7 +232,7 @@ struct pfsync_bucket
 #define	PFSYNCF_BUCKET_PUSH	0x00000001
 
 	size_t			b_len;
-	TAILQ_HEAD(, pf_kstate)			b_qs[PFSYNC_S_COUNT];
+	TAILQ_HEAD(, pf_kstate)			b_qs[PFSYNC_Q_COUNT];
 	TAILQ_HEAD(, pfsync_upd_req_item)	b_upd_req_list;
 	TAILQ_HEAD(, pfsync_deferral)		b_deferrals;
 	u_int			b_deferred;
@@ -220,6 +252,7 @@ struct pfsync_softc {
 	uint8_t			sc_maxupdates;
 	union inet_template     sc_template;
 	struct mtx		sc_mtx;
+	uint32_t		sc_version;
 
 	/* Queued data */
 	struct pfsync_bucket	*sc_buckets;
@@ -336,7 +369,8 @@ pfsync_clone_create(struct if_clone *ifc, int unit, caddr_t param)
 	struct pfsync_softc *sc;
 	struct ifnet *ifp;
 	struct pfsync_bucket *b;
-	int c, q;
+	int c;
+	enum pfsync_q_id q;
 
 	if (unit != 0)
 		return (EINVAL);
@@ -347,6 +381,7 @@ pfsync_clone_create(struct if_clone *ifc, int unit, caddr_t param)
 	sc = malloc(sizeof(struct pfsync_softc), M_PFSYNC, M_WAITOK | M_ZERO);
 	sc->sc_flags |= PFSYNCF_OK;
 	sc->sc_maxupdates = 128;
+	sc->sc_version = PFSYNC_MSG_VERSION_DEFAULT;
 
 	ifp = sc->sc_ifp = if_alloc(IFT_PFSYNC);
 	if (ifp == NULL) {
@@ -379,7 +414,7 @@ pfsync_clone_create(struct if_clone *ifc, int unit, caddr_t param)
 		b->b_sc = sc;
 		b->b_len = PFSYNC_MINPKT;
 
-		for (q = 0; q < PFSYNC_S_COUNT; q++)
+		for (q = 0; q < PFSYNC_Q_COUNT; q++)
 			TAILQ_INIT(&b->b_qs[q]);
 
 		TAILQ_INIT(&b->b_upd_req_list);
@@ -465,7 +500,7 @@ pfsync_alloc_scrub_memory(struct pfsync_state_peer *s,
 }
 
 static int
-pfsync_state_import(struct pfsync_state *sp, int flags)
+pfsync_state_import(union pfsync_state_union *sp, int flags, int msg_version)
 {
 	struct pfsync_softc *sc = V_pfsyncif;
 #ifndef	__NO_STRICT_ALIGNMENT
@@ -480,17 +515,17 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 
 	PF_RULES_RASSERT();
 
-	if (sp->creatorid == 0) {
+	if (sp->pfs_1301.creatorid == 0) {
 		if (V_pf_status.debug >= PF_DEBUG_MISC)
 			printf("%s: invalid creator id: %08x\n", __func__,
-			    ntohl(sp->creatorid));
+			    ntohl(sp->pfs_1301.creatorid));
 		return (EINVAL);
 	}
 
-	if ((kif = pfi_kkif_find(sp->ifname)) == NULL) {
+	if ((kif = pfi_kkif_find(sp->pfs_1301.ifname)) == NULL) {
 		if (V_pf_status.debug >= PF_DEBUG_MISC)
 			printf("%s: unknown interface: %s\n", __func__,
-			    sp->ifname);
+			    sp->pfs_1301.ifname);
 		if (flags & PFSYNC_SI_IOCTL)
 			return (EINVAL);
 		return (0);	/* skip this state */
@@ -500,11 +535,11 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 	 * If the ruleset checksums match or the state is coming from the ioctl,
 	 * it's safe to associate the state with the rule of that number.
 	 */
-	if (sp->rule != htonl(-1) && sp->anchor == htonl(-1) &&
-	    (flags & (PFSYNC_SI_IOCTL | PFSYNC_SI_CKSUM)) && ntohl(sp->rule) <
+	if (sp->pfs_1301.rule != htonl(-1) && sp->pfs_1301.anchor == htonl(-1) &&
+	    (flags & (PFSYNC_SI_IOCTL | PFSYNC_SI_CKSUM)) && ntohl(sp->pfs_1301.rule) <
 	    pf_main_ruleset.rules[PF_RULESET_FILTER].active.rcount)
 		r = pf_main_ruleset.rules[
-		    PF_RULESET_FILTER].active.ptr_array[ntohl(sp->rule)];
+		    PF_RULESET_FILTER].active.ptr_array[ntohl(sp->pfs_1301.rule)];
 	else
 		r = &V_pf_default_rule;
 
@@ -523,16 +558,16 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 		goto cleanup;
 
 #ifndef	__NO_STRICT_ALIGNMENT
-	bcopy(&sp->key, key, sizeof(struct pfsync_state_key) * 2);
+	bcopy(&sp->pfs_1301.key, key, sizeof(struct pfsync_state_key) * 2);
 	kw = &key[PF_SK_WIRE];
 	ks = &key[PF_SK_STACK];
 #else
-	kw = &sp->key[PF_SK_WIRE];
-	ks = &sp->key[PF_SK_STACK];
+	kw = &sp->pfs_1301.key[PF_SK_WIRE];
+	ks = &sp->pfs_1301.key[PF_SK_STACK];
 #endif
 
-	if (PF_ANEQ(&kw->addr[0], &ks->addr[0], sp->af) ||
-	    PF_ANEQ(&kw->addr[1], &ks->addr[1], sp->af) ||
+	if (PF_ANEQ(&kw->addr[0], &ks->addr[0], sp->pfs_1301.af) ||
+	    PF_ANEQ(&kw->addr[1], &ks->addr[1], sp->pfs_1301.af) ||
 	    kw->port[0] != ks->port[0] ||
 	    kw->port[1] != ks->port[1]) {
 		sks = uma_zalloc(V_pf_state_key_z, M_NOWAIT);
@@ -542,8 +577,8 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 		sks = skw;
 
 	/* allocate memory for scrub info */
-	if (pfsync_alloc_scrub_memory(&sp->src, &st->src) ||
-	    pfsync_alloc_scrub_memory(&sp->dst, &st->dst))
+	if (pfsync_alloc_scrub_memory(&sp->pfs_1301.src, &st->src) ||
+	    pfsync_alloc_scrub_memory(&sp->pfs_1301.dst, &st->dst))
 		goto cleanup;
 
 	/* Copy to state key(s). */
@@ -551,69 +586,110 @@ pfsync_state_import(struct pfsync_state *sp, int flags)
 	skw->addr[1] = kw->addr[1];
 	skw->port[0] = kw->port[0];
 	skw->port[1] = kw->port[1];
-	skw->proto = sp->proto;
-	skw->af = sp->af;
+	skw->proto = sp->pfs_1301.proto;
+	skw->af = sp->pfs_1301.af;
 	if (sks != skw) {
 		sks->addr[0] = ks->addr[0];
 		sks->addr[1] = ks->addr[1];
 		sks->port[0] = ks->port[0];
 		sks->port[1] = ks->port[1];
-		sks->proto = sp->proto;
-		sks->af = sp->af;
+		sks->proto = sp->pfs_1301.proto;
+		sks->af = sp->pfs_1301.af;
 	}
 
 	/* copy to state */
-	bcopy(&sp->rt_addr, &st->rt_addr, sizeof(st->rt_addr));
-	st->creation = time_uptime - ntohl(sp->creation);
+	bcopy(&sp->pfs_1301.rt_addr, &st->rt_addr, sizeof(st->rt_addr));
+	st->creation = time_uptime - ntohl(sp->pfs_1301.creation);
 	st->expire = time_uptime;
-	if (sp->expire) {
+	if (sp->pfs_1301.expire) {
 		uint32_t timeout;
 
-		timeout = r->timeout[sp->timeout];
+		timeout = r->timeout[sp->pfs_1301.timeout];
 		if (!timeout)
-			timeout = V_pf_default_rule.timeout[sp->timeout];
+			timeout = V_pf_default_rule.timeout[sp->pfs_1301.timeout];
 
 		/* sp->expire may have been adaptively scaled by export. */
-		st->expire -= timeout - ntohl(sp->expire);
+		st->expire -= timeout - ntohl(sp->pfs_1301.expire);
 	}
 
-	st->direction = sp->direction;
-	st->log = sp->log;
-	st->timeout = sp->timeout;
-	/* 8 from old peers, 16 bits from new peers */
-	st->state_flags = sp->state_flags_compat | ntohs(sp->state_flags);
+	st->direction = sp->pfs_1301.direction;
+	st->log = sp->pfs_1301.log;
+	st->timeout = sp->pfs_1301.timeout;
 
-	if (r == &V_pf_default_rule) {
-		/* ToS and Prio are not sent over struct pfsync_state */
-		st->state_flags &= ~PFSTATE_SETMASK;
-	} else {
-		/* Most actions are applied form state, not from rule. Until
-		 * pfsync can forward all those actions and their parameters we
-		 * must relay on restoring them from the found rule.
-		 * It's a copy of pf_rule_to_actions() */
-		st->qid = r->qid;
-		st->pqid = r->pqid;
-		st->rtableid = r->rtableid;
-		if (r->scrub_flags & PFSTATE_SETTOS)
-			st->set_tos = r->set_tos;
-		st->min_ttl = r->min_ttl;
-		st->max_mss = r->max_mss;
-		st->state_flags |= (r->scrub_flags & (PFSTATE_NODF|PFSTATE_RANDOMID|
-		    PFSTATE_SETTOS|PFSTATE_SCRUB_TCP|PFSTATE_SETPRIO));
-		st->dnpipe = r->dnpipe;
-		st->dnrpipe = r->dnrpipe;
-		/* FIXME: dnflags are not part of state, can't update them */
-	}
-
-	st->id = sp->id;
-	st->creatorid = sp->creatorid;
-	pf_state_peer_ntoh(&sp->src, &st->src);
-	pf_state_peer_ntoh(&sp->dst, &st->dst);
+	switch (msg_version) {
+		case PFSYNC_MSG_VERSION_1301:
+			st->state_flags = sp->pfs_1301.state_flags;
+			/*
+			 * In FreeBSD 13 pfsync lacks many attributes. Copy them
+			 * from the rule if possible. If rule can't be matched
+			 * clear any set options as we can't recover their
+			 * parameters.
+			*/
+			if (r == &V_pf_default_rule) {
+				st->state_flags &= ~PFSTATE_SETMASK;
+			} else {
+				/*
+				 * Similar to pf_rule_to_actions(). This code
+				 * won't set the actions properly if they come
+				 * from multiple "match" rules as only rule
+				 * creating the state is send over pfsync.
+				 */
+				st->qid = r->qid;
+				st->pqid = r->pqid;
+				st->rtableid = r->rtableid;
+				if (r->scrub_flags & PFSTATE_SETTOS)
+					st->set_tos = r->set_tos;
+				st->min_ttl = r->min_ttl;
+				st->max_mss = r->max_mss;
+				st->state_flags |= (r->scrub_flags &
+				    (PFSTATE_NODF|PFSTATE_RANDOMID|
+				    PFSTATE_SETTOS|PFSTATE_SCRUB_TCP|
+				    PFSTATE_SETPRIO));
+				if (r->dnpipe || r->dnrpipe) {
+					if (r->free_flags & PFRULE_DN_IS_PIPE)
+						st->state_flags |= PFSTATE_DN_IS_PIPE;
+					else
+						st->state_flags &= ~PFSTATE_DN_IS_PIPE;
+				}
+				st->dnpipe = r->dnpipe;
+				st->dnrpipe = r->dnrpipe;
+			}
+			break;
+		case PFSYNC_MSG_VERSION_1400:
+			st->state_flags = ntohs(sp->pfs_1400.state_flags);
+			st->qid = ntohs(sp->pfs_1400.qid);
+			st->pqid = ntohs(sp->pfs_1400.pqid);
+			st->dnpipe = ntohs(sp->pfs_1400.dnpipe);
+			st->dnrpipe = ntohs(sp->pfs_1400.dnrpipe);
+			st->rtableid = ntohl(sp->pfs_1400.rtableid);
+			st->min_ttl = sp->pfs_1400.min_ttl;
+			st->set_tos = sp->pfs_1400.set_tos;
+			st->max_mss = ntohs(sp->pfs_1400.max_mss);
+			st->set_prio[0] = sp->pfs_1400.set_prio[0];
+			st->set_prio[1] = sp->pfs_1400.set_prio[1];
+			st->rt = sp->pfs_1400.rt;
+			if (st->rt && (st->rt_kif = pfi_kkif_find(sp->pfs_1400.rt_ifname)) == NULL) {
+				if (V_pf_status.debug >= PF_DEBUG_MISC)
+					printf("%s: unknown route interface: %s\n",
+					    __func__, sp->pfs_1400.rt_ifname);
+				if (flags & PFSYNC_SI_IOCTL)
+					return (EINVAL);
+				return (0);	/* skip this state */
+			}
+			break;
+		default:
+			panic("%s: Unsupported pfsync_msg_version %d",
+			    __func__, msg_version);
+	}
+
+	st->id = sp->pfs_1301.id;
+	st->creatorid = sp->pfs_1301.creatorid;
+	pf_state_peer_ntoh(&sp->pfs_1301.src, &st->src);
+	pf_state_peer_ntoh(&sp->pfs_1301.dst, &st->dst);
 
 	st->rule.ptr = r;
 	st->nat_rule.ptr = NULL;
 	st->anchor.ptr = NULL;
-	st->rt_kif = NULL;
 
 	st->pfsync_time = time_uptime;
 	st->sync_state = PFSYNC_S_NONE;
@@ -745,7 +821,7 @@ pfsync_input(struct mbuf **mp, int *offp __unused, int proto __unused)
 
 		count = ntohs(subh.count);
 		V_pfsyncstats.pfsyncs_iacts[subh.action] += count;
-		rv = (*pfsync_acts[subh.action])(m, offset, count, flags);
+		rv = (*pfsync_acts[subh.action])(m, offset, count, flags, subh.action);
 		if (rv == -1) {
 			PF_RULES_RUNLOCK();
 			return (IPPROTO_DONE);
@@ -762,7 +838,7 @@ done:
 #endif
 
 static int
-pfsync_in_clr(struct mbuf *m, int offset, int count, int flags)
+pfsync_in_clr(struct mbuf *m, int offset, int count, int flags, int action)
 {
 	struct pfsync_clr *clr;
 	struct mbuf *mp;
@@ -804,36 +880,50 @@ relock:
 }
 
 static int
-pfsync_in_ins(struct mbuf *m, int offset, int count, int flags)
+pfsync_in_ins(struct mbuf *m, int offset, int count, int flags, int action)
 {
 	struct mbuf *mp;
-	struct pfsync_state *sa, *sp;
-	int len = sizeof(*sp) * count;
-	int i, offp;
+	union pfsync_state_union *sa, *sp;
+	int i, offp, len, msg_version;
+
+	switch (action) {
+		case PFSYNC_ACT_INS_1301:
+			len = sizeof(struct pfsync_state_1301) * count;
+			msg_version = PFSYNC_MSG_VERSION_1301;
+			break;
+		case PFSYNC_ACT_INS_1400:
+			len = sizeof(struct pfsync_state_1400) * count;
+			msg_version = PFSYNC_MSG_VERSION_1400;
+			break;
+		default:
+			V_pfsyncstats.pfsyncs_badact++;
+			return (-1);
+	}
 
 	mp = m_pulldown(m, offset, len, &offp);
 	if (mp == NULL) {
 		V_pfsyncstats.pfsyncs_badlen++;
 		return (-1);
 	}
-	sa = (struct pfsync_state *)(mp->m_data + offp);
+	sa = (union pfsync_state_union *)(mp->m_data + offp);
 
 	for (i = 0; i < count; i++) {
 		sp = &sa[i];
 
 		/* Check for invalid values. */
-		if (sp->timeout >= PFTM_MAX ||
-		    sp->src.state > PF_TCPS_PROXY_DST ||
-		    sp->dst.state > PF_TCPS_PROXY_DST ||
-		    sp->direction > PF_OUT ||
-		    (sp->af != AF_INET && sp->af != AF_INET6)) {
+		if (sp->pfs_1301.timeout >= PFTM_MAX ||
+		    sp->pfs_1301.src.state > PF_TCPS_PROXY_DST ||
+		    sp->pfs_1301.dst.state > PF_TCPS_PROXY_DST ||
+		    sp->pfs_1301.direction > PF_OUT ||
+		    (sp->pfs_1301.af != AF_INET &&
+		    sp->pfs_1301.af != AF_INET6)) {
 			if (V_pf_status.debug >= PF_DEBUG_MISC)
 				printf("%s: invalid value\n", __func__);
 			V_pfsyncstats.pfsyncs_badval++;
 			continue;
 		}
 
-		if (pfsync_state_import(sp, flags) == ENOMEM)
+		if (pfsync_state_import(sp, flags, msg_version) == ENOMEM)
 			/* Drop out, but process the rest of the actions. */
 			break;
 	}
@@ -842,7 +932,7 @@ pfsync_in_ins(struct mbuf *m, int offset, int count, int flags)
 }
 
 static int
-pfsync_in_iack(struct mbuf *m, int offset, int count, int flags)
+pfsync_in_iack(struct mbuf *m, int offset, int count, int flags, int action)
 {
 	struct pfsync_ins_ack *ia, *iaa;
 	struct pf_kstate *st;
@@ -913,31 +1003,42 @@ pfsync_upd_tcp(struct pf_kstate *st, struct pfsync_state_peer *src,
 }
 
 static int
-pfsync_in_upd(struct mbuf *m, int offset, int count, int flags)
+pfsync_in_upd(struct mbuf *m, int offset, int count, int flags, int action)
 {
 	struct pfsync_softc *sc = V_pfsyncif;
-	struct pfsync_state *sa, *sp;
+	union pfsync_state_union *sa, *sp;
 	struct pf_kstate *st;
-	int sync;
-
 	struct mbuf *mp;
-	int len = count * sizeof(*sp);
-	int offp, i;
+	int sync, offp, i, len, msg_version;
+
+	switch (action) {
*** 1017 LINES SKIPPED ***