--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/Makefile	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,58 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+PROG= in.ikev2d
+
+include ../../../Makefile.cmd
+
+SUBDIRS=
+$(INTEL_BLD)SUBDIRS += $(MACH)
+$(BUILD64)SUBDIRS += $(MACH64)
+
+all	:=	TARGET = all
+install	:=	TARGET = install
+clean	:=	TARGET = clean
+clobber	:=	TARGET = clobber
+lint	:=	TARGET = lint
+
+.KEEP_STATE:
+
+all:	$(SUBDIRS)
+
+clean clobber lint:	$(SUBDIRS)
+
+include ../Makefile.lib
+
+install:	$(SUBDIRS)
+	-$(RM) $(ROOTLIBINETPROG)
+	-$(LN) $(ISAEXEC) $(ROOTLIBINETPROG)
+
+$(SUBDIRS):	FRC
+	@cd $@; pwd; $(MAKE) $(TARGET)
+
+FRC:
+
+include ../../../Makefile.targ
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/Makefile.com	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,96 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Copyright 2017 Jason King.
+# Copyright (c) 2017, Joyent, Inc.
+#
+
+PROG = in.ikev2d
+
+OBJS =	buf.o		\
+	config.o	\
+	config_parse.o	\
+	dh.o		\
+	fromto.o	\
+	ikev2_common.o	\
+	ikev2_enum.o	\
+	ikev2_pkt.o	\
+	ikev2_proto.o	\
+	ikev2_sa.o	\
+	ikev2_sa_init.o	\
+	inbound.o	\
+	main.o		\
+	pfkey.o		\
+	preshared.o	\
+	prf.o		\
+	pkcs11.o	\
+	pkt.o		\
+	random.o	\
+	timer.o		\
+	worker.o
+	
+SRCS = $(OBJS:%.o=../common/%.c)
+
+include ../../../../Makefile.cmd
+include ../../Makefile.lib
+
+CPPFLAGS += -D__EXTENSIONS__
+CPPFLAGS += -DHAVE_POSIX_STYLE_SOCKET_PROTOTYPES -D_POSIX_PTHREAD_SEMANTICS
+
+# Use X/Open sockets for fromto.c
+fromto.o := CPPFLAGS += -D_XOPEN_SOURCE=600
+
+# ... but as a consequence, you have to disable a few lint checks.
+LINTFLAGS += -erroff=E_INCONS_ARG_DECL2 -erroff=E_INCONS_VAL_TYPE_DECL2
+LINTFLAGS64 += -erroff=E_INCONS_ARG_DECL2 -erroff=E_INCONS_VAL_TYPE_DECL2
+
+C99MODE = $(C99_ENABLE)
+CFLAGS += $(CCVERBOSE) -D_REENTRANT
+CFLAGS64 += $(CCVERBOSE) -D_REENTRANT
+LDLIBS += -lnsl -lsecdb -lumem -lxnet -lipsecutil -lpkcs11 -lcryptoutil
+LDLIBS += -lnvpair -luutil -lbunyan
+
+FILEMODE = 0555
+GROUP = bin
+
+CLEANFILES += $(OBJS)
+
+.KEEP_STATE:
+
+.PARALLEL:
+
+all: $(PROG)
+
+$(PROG): $(OBJS)
+	$(LINK.c) -o $@ $(OBJS) $(LDLIBS)
+	$(POST_PROCESS) ; $(STRIP_STABS)
+
+clean:
+	-$(RM) $(CLEANFILES)
+
+lint: lint_SRCS
+
+%.o: ../common/%.c
+	$(COMPILE.c) $<
+
+include ../../../../Makefile.targ
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/THIRDPARTYLICENSE	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,26 @@
+ * Copyright (C) 1995-2009 WIDE Project.
+ * All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/THIRDPARTYLICENSE.descrip	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,1 @@
+PORTIONS OF IKEv2 FROM "racoon2"
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/amd64/Makefile	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,30 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+include ../../../../Makefile.cmd.64
+
+install: all $(ROOTLIBINETPROG64)
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/buf.c	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,203 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+#include <sys/debug.h>
+#include <string.h>
+#include <umem.h>
+#include "buf.h"
+
+static inline boolean_t
+eof_check(buf_t *buf, size_t len)
+{
+	if (buf_eof(buf))
+		return (B_TRUE);
+	if (buf->b_ptr + len > buf->b_buf + buf->b_len) {
+		buf->b_flags |= BUF_EOF;
+		return (B_TRUE);
+	}
+	return (B_FALSE);
+}
+
+size_t
+buf_cat(buf_t *restrict dest, const buf_t *restrict src, size_t n)
+{
+	size_t total = 0;
+
+	BUF_IS_WRITE(dest);
+
+	for (size_t i = 0; i < n; i++, src++) {
+		size_t amt = buf_left(src);
+
+		BUF_IS_READ(src);
+		if (eof_check(dest, amt))
+			return (total);
+
+		(void) memcpy(dest->b_ptr, src->b_ptr, amt);
+		dest->b_ptr += amt;
+		total += amt;
+	}
+
+	return (total);
+}
+
+size_t
+buf_copy(buf_t *restrict dest, const buf_t *restrict src, size_t n)
+{
+	uchar_t *p = dest->b_buf;
+	size_t total = 0;
+
+	BUF_IS_WRITE(dest);
+
+	dest->b_ptr = dest->b_buf;
+	return (buf_cat(dest, src, n));
+	for (size_t i = 0; i < n; i++) {
+		size_t amt = src[i].b_len;
+
+		if (total + amt > dest->b_len)
+			amt = dest->b_len - total;
+		if (amt == 0)
+			break;
+
+		(void) memcpy(p, src[i].b_buf, amt);
+		total += amt;
+		p += amt;
+	}
+
+
+	/* XXX: what to do about b_ptr? */
+	return (total);
+}
+
+void
+buf_clear(buf_t *buf)
+{
+	if (buf == NULL || buf->b_buf == NULL || buf->b_len == 0)
+		return;
+
+	(void) memset(buf->b_buf, 0, buf->b_len);
+	buf->b_ptr = buf->b_buf;
+	buf->b_flags &= ~(BUF_EOF);
+}
+
+void
+buf_range(buf_t *restrict dest, buf_t *restrict src, size_t off, size_t len)
+{
+	ASSERT(off + len <= src->len);
+	dest->b_ptr = dest->b_buf = src->b_ptr + off;
+	dest->b_len = src->b_len - off;
+	dest->b_flags = src->b_flags & ~(BUF_ALLOCED);
+}
+
+boolean_t
+buf_alloc(buf_t *buf, size_t len)
+{
+	if ((buf->b_buf = umem_zalloc(len, UMEM_DEFAULT)) == NULL)
+		return (B_FALSE);
+	buf->b_ptr = buf->b_buf;
+	buf->b_len = len;
+	buf->b_flags = BUF_ALLOCED;
+	return (B_TRUE);
+}
+
+void
+buf_free(buf_t *buf)
+{
+	if (buf == NULL)
+		return;
+
+	VERIFY(buf->b_flags & BUF_ALLOCED);
+	buf_clear(buf);
+	umem_free(buf->b_buf, buf->b_len);
+}
+
+int
+buf_cmp(const buf_t *restrict l, const buf_t *restrict r)
+{
+	size_t minlen;
+	int cmp;
+
+	/* !NULL > NULL, NULL == NULL */
+	if (r == NULL || r->b_len == 0 || r->b_ptr == NULL) {
+		if (l != NULL && l->b_len > 0 && l->b_ptr != NULL)
+			return (1);
+		else
+			return (0);
+	}
+
+	/* NULL < !NULL */
+	if (l == NULL || l->b_len == 0 || l->b_ptr == NULL)
+		return (1);
+
+	minlen = l->b_len;
+	if (r->b_len < minlen)
+		minlen = r->b_len;
+
+	cmp = memcmp(l->b_buf, r->b_buf, minlen);
+	if (cmp != 0)
+		return ((cmp > 0) ? 1 : - 1);
+
+	if (l->b_len > r->b_len)
+		return (1);
+	if (l->b_len < r->b_len)
+		return (-1);
+	return (0);
+}
+
+void
+buf_put8(buf_t *buf, uint8_t val)
+{
+	BUF_IS_WRITE(buf);
+	if (eof_check(buf, sizeof (uint8_t)))
+		return;
+
+	*(buf->b_ptr++) = val;
+}
+
+void
+buf_put64(buf_t *buf, uint64_t val)
+{
+	BUF_IS_WRITE(buf);
+	if (eof_check(buf, sizeof (uint64_t)))
+		return;
+
+	*(buf->b_ptr++) = (uchar_t)((val >> 56) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 48) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 40) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 32) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 24) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 16) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)((val >> 8) & 0xffLL);
+	*(buf->b_ptr++) = (uchar_t)(val & 0xffLL);
+}
+
+void
+buf_put32(buf_t *buf, uint32_t val)
+{
+	BUF_IS_WRITE(buf);
+	if (eof_check(buf, sizeof (uint32_t)))
+		return;
+
+	*(buf->b_ptr++) = (uchar_t)((val >> 24) & (uint32_t)0xff);
+	*(buf->b_ptr++) = (uchar_t)((val >> 16) & (uint32_t)0xff);
+	*(buf->b_ptr++) = (uchar_t)((val >> 8) & (uint32_t)0xff);
+	*(buf->b_ptr++) = (uchar_t)(val & (uint32_t)0xffLL);
+}
+
+extern boolean_t buf_eof(const buf_t *);
+extern size_t buf_left(const buf_t *);
+extern void buf_reset(buf_t *);
+extern void buf_skip(buf_t *, size_t);
+extern void buf_set_read(buf_t *);
+extern void buf_set_write(buf_t *);
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/buf.h	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,132 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _BUF_H
+#define	_BUF_H
+
+#include <sys/types.h>
+#include <sys/debug.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef struct buf {
+	uchar_t	*b_buf;
+	uchar_t *b_ptr;
+	size_t	b_len;
+	ulong_t	b_flags;
+} buf_t;
+#define	BUF_READ	(1UL << 0)
+#define	BUF_WRITE	(1UL << 1)
+#define	BUF_EOF		(1UL << 2)
+#define	BUF_ALLOCED	(1UL << 3)
+
+#define	STRUCT_TO_BUF(st) { 		\
+	.b_buf = (uchar_t *)&(st),	\
+	.b_ptr = (uchar_t *)&(st),	\
+	.b_len = sizeof ((st)),		\
+	.b_flags = 0			\
+}
+
+#define	BUF_IS_WRITE(b) \
+	VERIFY(((b)->b_flags & BUF_WRITE) && !((b)->b_flags & BUF_READ))
+#define	BUF_IS_READ(b) \
+	VERIFY(((b)->b_flags & BUF_READ) && !((b)->b_flags & BUF_WRITE))
+
+inline boolean_t
+buf_eof(const buf_t *b)
+{
+	return (!!(b->b_flags & BUF_EOF));
+}
+
+inline void
+buf_skip(buf_t *buf, size_t amt)
+{
+	if (buf_eof(buf))
+		return;
+
+	uchar_t *end = buf->b_buf + buf->b_len;
+	buf->b_ptr += amt;
+
+	if (buf->b_ptr > end) {
+		buf->b_ptr = end;
+		buf->b_flags |= BUF_EOF;
+	}
+}
+
+inline void
+buf_reset(buf_t *buf)
+{
+	buf->b_ptr = buf->b_buf;
+	buf->b_flags &= ~(BUF_EOF);
+}
+
+inline size_t
+buf_left(const buf_t *buf)
+{
+	if (buf->b_flags & BUF_EOF)
+		return (0);
+	return (buf->b_len - (size_t)(buf->b_ptr - buf->b_buf));
+}
+
+inline void
+buf_set_read(buf_t *buf)
+{
+	buf->b_flags &= ~(BUF_READ|BUF_WRITE);
+	buf->b_flags |= BUF_READ;
+}
+
+inline void
+buf_set_write(buf_t *buf)
+{
+	buf->b_flags &= ~(BUF_READ|BUF_WRITE);
+	buf->b_flags |= BUF_WRITE;
+}
+
+size_t		buf_cat(buf_t *restrict, const buf_t *restrict, size_t);
+size_t		buf_copy(buf_t *restrict, const buf_t *restrict, size_t);
+void		buf_clear(buf_t *);
+void		buf_range(buf_t *restrict, buf_t *restrict, size_t, size_t);
+
+boolean_t	buf_alloc(buf_t *, size_t);
+void		buf_free(buf_t *);
+
+int		buf_cmp(const buf_t *restrict, const buf_t *restrict);
+
+void		buf_put8(buf_t *, uint8_t);
+void		buf_put32(buf_t *, uint32_t);
+void		buf_put64(buf_t *, uint64_t);
+
+#if 0
+void		buf_init(buf_t *, char *, size_t, size_t, boolean_t);
+void		buf_range(buf_t *, size_t, buf_t *);
+
+uint8_t		buf_get8(buf_t *);
+uint16_t	buf_get16(buf_t *);
+uint32_t	buf_get32(buf_t *);
+uint64_t	buf_get64(buf_t *);
+size_t		buf_copyfrom(buf_t *, char *, size_t);
+
+void		buf_put16(buf_t *, uint16_t);
+size_t		buf_append(char *, size_t, buf_t *);
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _BUF_H */
--- /dev/null	Wed Aug 23 21:32:32 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/config.c	Wed Aug 23 21:32:32 2017
@@ -0,0 +1,204 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <pthread.h>
+#include "defs.h"
+#include "config.h"
+#include "ikev2_enum.h"
+
+pthread_rwlock_t cfg_lock = PTHREAD_RWLOCK_INITIALIZER;
+config_t *config;
+
+static boolean_t cfg_addr_match(const sockaddr_u_t *restrict,
+    const config_addr_t *restrict);
+
+config_t *
+config_get(void)
+{
+	config_t *cfg = NULL;
+
+	PTH(pthread_rwlock_rdlock(&cfg_lock));
+	cfg = config;
+	CONFIG_REFHOLD(cfg);
+	PTH(pthread_rwlock_unlock(&cfg_lock));
+	return (cfg);
+}
+
+void
+config_xf_log(bunyan_logger_t *b, bunyan_level_t level, const char *msg,
+    const config_xf_t *xf)
+{
+	getlog(level)(b, msg,
+	    BUNYAN_T_STRING, "xf_encralg", ikev2_xf_encr_str(xf->xf_encr),
+	    BUNYAN_T_UINT32, "xf_minbits", (uint32_t)xf->xf_minbits,
+	    BUNYAN_T_UINT32, "xf_maxbits", (uint32_t)xf->xf_maxbits,
+	    BUNYAN_T_STRING, "xf_authalg", ikev2_xf_auth_str(xf->xf_auth),
+	    BUNYAN_T_STRING, "xf_authtype",
+	    ikev2_auth_type_str(xf->xf_authtype),
+	    BUNYAN_T_STRING, "xf_dh", ikev2_dh_str(xf->xf_dh),
+	    BUNYAN_T_END);
+}
+
+/*
+ * Return the first rule that matches the given local and remote addresses.
+ * If no rule matches, NULL is returned.  The config_t instance that holds
+ * the given rule is refheld.
+ */
+config_rule_t *
+config_get_rule(sockaddr_u_t *restrict local, sockaddr_u_t *restrict remote)
+{
+	config_t *cfg = config_get();
+
+	for (size_t i = 0; cfg->cfg_rules[i] != NULL; i++) {
+		config_rule_t *rule = cfg->cfg_rules[i];
+		boolean_t local_match = B_FALSE;
+		boolean_t remote_match = B_FALSE;
+
+		for (size_t j = 0; j < rule->rule_nlocal_addr; j++) {
+			if (cfg_addr_match(local, &rule->rule_local_addr[j])) {
+				local_match = B_TRUE;
+				break;
+			}
+		}
+		for (size_t j = 0; j < rule->rule_nremote_addr; j++) {
+			if (cfg_addr_match(remote,
+			    &rule->rule_remote_addr[j])) {
+				remote_match = B_TRUE;
+				break;
+			}
+		}
+		if (local_match && remote_match)
+			return (rule);
+	}
+
+	CONFIG_REFRELE(cfg);
+	return (NULL);
+}
+
+static boolean_t
+cfg_addr_match(const sockaddr_u_t *restrict l, const config_addr_t *restrict r)
+{
+	uint32_t mask;
+
+	switch (r->cfa_type) {
+	case CFG_ADDR_IPV4:
+		if (l->sau_ss->ss_family != AF_INET)
+			return (B_FALSE);
+		if (l->sau_sin->sin_addr.s_addr != r->cfa_startu.cfa_ip4)
+			return (B_FALSE);
+		return (B_TRUE);
+	case CFG_ADDR_IPV4_PREFIX:
+		/* XXX: this needs testing */
+		if (l->sau_ss->ss_family != AF_INET)
+			return (B_FALSE);
+		mask = (0xffffffff << (32 - r->cfa_endu.cfa_num)) &
+		    0xffffffff;
+		if ((l->sau_sin->sin_addr.s_addr & mask) ==
+		    (r->cfa_startu.cfa_ip4 &mask))
+			return (B_TRUE);
+		return (B_FALSE);
+	case CFG_ADDR_IPV4_RANGE:
+		if (l->sau_ss->ss_family != AF_INET)
+			return (B_FALSE);
+		if (l->sau_sin->sin_addr.s_addr >= r->cfa_startu.cfa_ip4 &&
+		    l->sau_sin->sin_addr.s_addr <= r->cfa_endu.cfa_ip4)
+			return (B_TRUE);
+		return (B_FALSE);
+	case CFG_ADDR_IPV6:
+		if (l->sau_ss->ss_family != AF_INET6)
+			return (B_FALSE);
+		if (IN6_ARE_ADDR_EQUAL(&l->sau_sin6->sin6_addr,
+		    &r->cfa_startu.cfa_ip6))
+			return (B_TRUE);
+		return (B_FALSE);
+	case CFG_ADDR_IPV6_PREFIX:
+		if (l->sau_ss->ss_family != AF_INET6)
+			return (B_FALSE);
+		if (IN6_ARE_PREFIXEDADDR_EQUAL(&l->sau_sin6->sin6_addr,
+		    &r->cfa_startu.cfa_ip6, r->cfa_endu.cfa_num))
+			return (B_TRUE);
+		return (B_FALSE);
+	case CFG_ADDR_IPV6_RANGE:
+		if (l->sau_ss->ss_family != AF_INET6)
+			return (B_FALSE);
+		for (size_t i = 0; i < 16; i++) {
+			if ((l->sau_sin6->sin6_addr.s6_addr[i] <
+			    r->cfa_startu.cfa_ip6.s6_addr[i]) ||
+			    (l->sau_sin6->sin6_addr.s6_addr[i] >
+			    r->cfa_endu.cfa_ip6.s6_addr[i]))
+				return (B_FALSE);
+		}
+		return (B_TRUE);
+	}
+	return (B_FALSE);
+}
+
+void
+cfg_rule_free(config_rule_t *rule)
+{
+	if (rule == NULL)
+		return;
+
+	if (rule->rule_xf != NULL) {
+		for (size_t i = 0; rule->rule_xf[i] != NULL; i++)
+			free(rule->rule_xf[i]);
+	}
+
+	free(rule->rule_xf);
+	free(rule->rule_local_addr);
+	free(rule->rule_remote_addr);
+	free(rule->rule_label);
+	free(rule);
+}
+
+void
+cfg_free(config_t *cfg)
+{
+	if (cfg == NULL)
+		return;
+
+	size_t i;
+
+	VERIFY3U(cfg->cfg_refcnt, ==, 0);
+	
+	for (i = 0;
+	    cfg->cfg_cert_root != NULL && cfg->cfg_cert_root[i] != NULL;
+	    i++)
+		free(cfg->cfg_cert_root[i]);
+
+	if (cfg->cfg_cert_trust != NULL) {
+		for (i = 0; cfg->cfg_cert_trust[i] != NULL; i++)
+			free(cfg->cfg_cert_trust[i]);
+	}
+
+	if (cfg->cfg_xforms != NULL) {
+		for (i = 0; cfg->cfg_xforms[i] != NULL; i++)
+			free(cfg->cfg_xforms[i]);
+	}
+
+	if (cfg->cfg_rules != NULL) {
+		for (i = 0; cfg->cfg_rules[i] != NULL; i++)
+			cfg_rule_free(cfg->cfg_rules[i]);
+	}
+
+	free(cfg->cfg_rules);
+	free(cfg->cfg_xforms);
+	free(cfg->cfg_proxy);
+	free(cfg->cfg_socks);
+	free(cfg->cfg_cert_root);
+	free(cfg->cfg_cert_trust);
+	free(cfg);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/config.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,158 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+#ifndef _CONFIG_H
+#define	_CONFIG_H
+
+#include <sys/types.h>
+#include <sys/time.h>
+#include <netinet/in.h>
+#include <stdio.h>
+#include <bunyan.h>
+#include <pthread.h>
+#include <atomic.h>
+#include "ikev2.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum config_auth_id_e {
+	CFG_AUTH_ID_DN,
+	CFG_AUTH_ID_DNS,
+	CFG_AUTH_ID_GN,
+	CFG_AUTH_ID_IPV4,
+	CFG_AUTH_ID_IPV4_PREFIX,
+	CFG_AUTH_ID_IPV4_RANGE,
+	CFG_AUTH_ID_IPV6,
+	CFG_AUTH_ID_IPV6_PREFIX,
+	CFG_AUTH_ID_IPV6_RANGE,
+	CFG_AUTH_ID_EMAIL
+} config_auth_id_t;
+
+typedef enum config_addr_e {
+	CFG_ADDR_IPV4,
+	CFG_ADDR_IPV4_PREFIX,
+	CFG_ADDR_IPV4_RANGE,
+	CFG_ADDR_IPV6,
+	CFG_ADDR_IPV6_PREFIX,
+	CFG_ADDR_IPV6_RANGE
+} config_addr_type_t;
+
+typedef struct config_addr_s {
+	config_addr_type_t	cfa_type;
+	union {
+		in_addr_t	cfa_ip4;
+		in6_addr_t	cfa_ip6;
+	} cfa_startu;
+	union {
+		in_addr_t	cfa_ip4;
+		in6_addr_t	cfa_ip6;
+		uint8_t		cfa_num;
+	} cfa_endu;
+} config_addr_t;
+
+typedef struct config_id_s {
+	config_auth_id_t	id_type;
+	union {
+		char *id_str;
+		struct {
+			uint8_t	*id_buf;
+			size_t	id_len;
+		} id_buf;
+		in_addr_t	id_ipv4;
+		in6_addr_t	id_ipv6;
+	} val;
+} config_id_t;
+
+typedef struct config_xf_s {
+	ikev2_xf_encr_t		xf_encr;
+	size_t			xf_minbits;
+	size_t			xf_maxbits;
+	ikev2_xf_auth_t		xf_auth;
+	ikev2_dh_t		xf_dh;
+	ikev2_auth_type_t	xf_authtype;
+	size_t			xf_lifetime_secs;
+	size_t			xf_nonce_len;
+} config_xf_t;
+
+struct config_s;
+typedef struct config_rule_s {
+	struct config_s		*rule_config;
+	char			*rule_label;
+	config_auth_id_t	rule_local_id_type;
+	config_addr_t		*rule_local_addr;
+	size_t			rule_nlocal_addr;
+	config_addr_t		*rule_remote_addr;
+	size_t			rule_nremote_addr;
+	config_id_t		*rule_id;
+	config_xf_t		**rule_xf;
+	size_t			rule_nxf;
+	ikev2_dh_t		rule_p2_dh;
+	char			*rule_local_id;
+	char			*rule_remote_id;
+} config_rule_t;
+
+struct config_s {
+	volatile uint32_t	cfg_refcnt;
+	config_rule_t		cfg_default;
+	config_rule_t		**cfg_rules;
+	size_t			cfg_rules_alloc;
+	config_xf_t		**cfg_xforms;
+	size_t			cfg_xforms_alloc;
+	char			*cfg_proxy;
+	char			*cfg_socks;
+	char			**cfg_cert_root;
+	size_t			cfg_cert_root_alloc;
+	char			**cfg_cert_trust;
+	size_t			cfg_cert_trust_alloc;
+	hrtime_t		cfg_expire_timer;	/* ns */
+	hrtime_t		cfg_lifetime_secs;	/* ns */
+	hrtime_t		cfg_retry_max;		/* ns */
+	hrtime_t		cfg_retry_init;		/* ns */
+	size_t			cfg_retry_limit;
+	boolean_t		cfg_ignore_crls;
+	boolean_t		cfg_use_http;
+	ikev2_dh_t		cfg_p2_pfs;
+	size_t			cfg_p1_lifetime_secs;
+	size_t			cfg_p1_nonce_len;
+	size_t			cfg_p2_lifetime_secs;
+	size_t			cfg_p2_softlife_secs;
+	size_t			cfg_p2_idletime_secs;
+	size_t			cfg_p2_lifetime_kb;
+	size_t			cfg_p2_softlife_kb;
+	size_t			cfg_p2_nonce_len;
+};
+typedef struct config_s config_t;
+#define	CONFIG_REFHOLD(cp) (void)atomic_inc_32(&(cp)->cfg_refcnt)
+#define	CONFIG_REFRELE(cp) \
+	(void) ((atomic_dec_32_nv(&(cp)->cfg_refcnt) != 0) || \
+	    (cfg_free(cp), 0))
+#define	RULE_IS_DEFAULT(r) (!!(&(r)->rule_config->cfg_default == (r)))
+
+extern pthread_rwlock_t cfg_lock;
+extern config_t *config;
+
+void process_config(FILE *, boolean_t, bunyan_logger_t *);
+config_t *config_get(void);
+config_rule_t *config_get_rule(sockaddr_u_t *restrict, sockaddr_u_t *restrict);
+void cfg_rule_free(config_rule_t *);
+void cfg_free(config_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _CONFIG_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/config_parse.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,1613 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+#include <sys/time.h>
+#include <sys/debug.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/sysmacros.h>
+#include <sys/stat.h>
+#include <arpa/inet.h>
+#include <fcntl.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <umem.h>
+#include <netinet/in.h>
+#include <errno.h>
+#include <bunyan.h>
+#include <ctype.h>
+#include <stdio.h>
+#include <err.h>
+#include "defs.h"
+#include "config.h"
+#include "ikev2.h"
+#include "ikev2_enum.h"
+
+#ifndef ARRAY_SIZE
+#define	ARRAY_SIZE(x)	(sizeof (x) / sizeof (x[0]))
+#endif
+
+#define	CONFIG_MAX	((size_t)1024*1024)
+#define	CONFIG_CHUNK	((size_t)1024)
+
+typedef enum keyword_e {
+	KW_NONE = 0,
+	KW_CERT_ROOT,
+	KW_CERT_TRUST,
+	KW_EXPIRE_TIMER,
+	KW_IGNORE_CRLS,
+	KW_LDAP_SERVER,
+	KW_PKCS11_PATH,
+	KW_RETRY_LIMIT,
+	KW_RETRY_TIMER_INIT,
+	KW_RETRY_TIMER_MAX,
+	KW_PROXY,
+	KW_SOCKS,
+	KW_USE_HTTP,
+	KW_P1_LIFETIME_SECS,
+	KW_P1_NONCE_LEN,
+	KW_P2_LIFETIME_SECS,
+	KW_P2_SOFTLIFE_SECS,
+	KW_P2_IDLETIME_SECS,
+	KW_P2_LIFETIME_KB,
+	KW_P2_SOFTLIFE_KB,
+	KW_P2_NONCE_LEN,
+	KW_LOCAL_ID_TYPE,
+	KW_P1_XFORM,
+	KW_AUTH_METHOD,
+	KW_OAKLEY_GROUP,
+	KW_AUTH_ALG,
+	KW_ENCR_ALG,
+	KW_LABEL,
+	KW_LOCAL_ADDR,
+	KW_REMOTE_ADDR,
+	KW_P2_PFS,
+	KW_LOCAL_ID,
+	KW_REMOTE_ID,
+	KW_MAX
+} keyword_t;
+
+static struct {
+	const char	*kw_str;
+	boolean_t	kw_has_arg;
+	boolean_t	kw_minus;
+} keyword_tab[] = {
+	{ "", B_FALSE },
+	{ "cert_root", B_TRUE, B_FALSE },
+	{ "cert_trust", B_TRUE, B_FALSE },
+	{ "expire_timer", B_TRUE, B_FALSE },
+	{ "ignore_crls", B_FALSE },
+	{ "ldap_server", B_TRUE, B_FALSE },
+	{ "pkcs11_path", B_TRUE, B_FALSE },
+	{ "retry_limit", B_TRUE, B_FALSE },
+	{ "retry_timer_init", B_TRUE, B_FALSE },
+	{ "retry_timer_max", B_TRUE, B_FALSE },
+	{ "proxy", B_TRUE, B_FALSE },
+	{ "socks", B_TRUE, B_FALSE },
+	{ "use_http", B_FALSE },
+	{ "p1_lifetime_secs", B_TRUE, B_FALSE },
+	{ "p1_nonce_len", B_TRUE, B_FALSE },
+	{ "p2_lifetime_secs", B_TRUE, B_FALSE },
+	{ "p2_softlife_secs", B_TRUE, B_FALSE },
+	{ "p2_idletime_secs", B_TRUE, B_FALSE },
+	{ "p2_lifetime_kb", B_TRUE, B_FALSE },
+	{ "p2_softlife_kb", B_TRUE, B_FALSE },
+	{ "p2_nonce_len", B_TRUE, B_FALSE },
+	{ "local_id_type", B_TRUE, B_FALSE },
+	{ "p1_xform", B_FALSE },
+	{ "auth_method", B_TRUE, B_FALSE },
+	{ "oakley_group", B_TRUE, B_FALSE },
+	{ "auth_alg", B_TRUE, B_FALSE },
+	{ "encr_alg", B_TRUE, B_FALSE },
+	{ "label", B_TRUE, B_FALSE },
+	{ "local_addr", B_TRUE, B_TRUE },
+	{ "remote_addr", B_TRUE, B_TRUE },
+	{ "p2_pfs", B_TRUE, B_FALSE },
+	{ "local_id", B_TRUE, B_FALSE },
+	{ "remote_id", B_TRUE, B_FALSE },
+};
+
+static struct {
+	ikev2_auth_type_t	a_id;
+	const char		*a_str;
+} auth_tab[] = {
+	{ IKEV2_AUTH_NONE, "" },
+	{ IKEV2_AUTH_RSA_SIG, "rsa_sig" },
+	{ IKEV2_AUTH_SHARED_KEY_MIC, "preshared" },
+	{ IKEV2_AUTH_DSS_SIG, "dss_sig" }
+};
+
+static struct {
+	config_auth_id_t	p1_id;
+	const char		*p1_str;
+} p1_id_tab[] = {
+	{ CFG_AUTH_ID_DN, "dn" },
+	{ CFG_AUTH_ID_DN, "DN" },
+	{ CFG_AUTH_ID_DNS, "dns" },
+	{ CFG_AUTH_ID_DNS, "DNS" },
+	{ CFG_AUTH_ID_DNS, "fqdn" },
+	{ CFG_AUTH_ID_DNS, "FQDN" },
+	{ CFG_AUTH_ID_GN, "gn" },
+	{ CFG_AUTH_ID_GN, "GN" },
+	{ CFG_AUTH_ID_IPV4, "ip" },
+	{ CFG_AUTH_ID_IPV4, "IP" },
+	{ CFG_AUTH_ID_IPV4, "ipv4" },
+	{ CFG_AUTH_ID_IPV4_PREFIX, "ipv4_prefix" },
+	{ CFG_AUTH_ID_IPV4_RANGE, "ipv4_range" },
+	{ CFG_AUTH_ID_IPV6, "ipv6" },
+	{ CFG_AUTH_ID_IPV6_PREFIX, "ipv6_prefix" },
+	{ CFG_AUTH_ID_IPV6_RANGE, "ipv6_range" },
+	{ CFG_AUTH_ID_EMAIL, "mbox" },
+	{ CFG_AUTH_ID_EMAIL, "MBOX" },
+	{ CFG_AUTH_ID_EMAIL, "user_fqdn" }
+};
+
+static struct {
+	ikev2_xf_auth_t xfa_id;
+	const char	*xfa_str;
+} xf_auth_tab[] = {
+	{ IKEV2_XF_AUTH_HMAC_MD5_128, "md5" },	/* XXX: verify this */
+	{ IKEV2_XF_AUTH_HMAC_SHA1_160, "sha" },
+	{ IKEV2_XF_AUTH_HMAC_SHA1_160, "sha1" },
+	{ IKEV2_XF_AUTH_HMAC_SHA2_256_128, "sha256" },
+	{ IKEV2_XF_AUTH_HMAC_SHA2_384_192, "sha384" },
+	{ IKEV2_XF_AUTH_HMAC_SHA2_512_256, "sha512" }
+};
+
+static struct {
+	ikev2_xf_encr_t xfe_id;
+	const char	*xfe_str;
+} xf_encr_tab[] = {
+	{ IKEV2_ENCR_DES, "des" },
+	{ IKEV2_ENCR_DES, "des-cbc" },
+	{ IKEV2_ENCR_3DES, "3des" },
+	{ IKEV2_ENCR_3DES, "3des-cbc" },
+	{ IKEV2_ENCR_BLOWFISH, "blowfish" },
+	{ IKEV2_ENCR_BLOWFISH, "blowfish-cbc" },
+	{ IKEV2_ENCR_AES_CBC, "aes" },
+	{ IKEV2_ENCR_AES_CBC, "aes-cbc" },
+	{ IKEV2_ENCR_AES_CCM_16, "aes-ccm" },
+	{ IKEV2_ENCR_AES_GCM_16, "aes-gcm" }
+};
+
+/*
+ * size_t would be a more appropriate type for t_{line,col}, but using
+ * uint32_t makes it cleaner for logging with bunyan
+ */
+typedef struct token {
+	char		*t_str;
+	const char	*t_linep;
+	uint32_t	t_line;
+	uint32_t	t_col;
+} token_t;
+
+typedef struct input {
+	char 	*in_buf;
+	size_t	in_buflen;
+	char	**in_lines;
+} input_t;
+
+typedef struct input_cursor {
+	input_t 	*ic_input;
+	char		*ic_p;
+	token_t		*ic_peek;
+	bunyan_logger_t	*ic_log;
+} input_cursor_t;
+
+static void add_str(char ***restrict, size_t *restrict, const char *restrict);
+static void add_xf(void *restrict, config_xf_t *restrict, boolean_t);
+static void add_rule(config_t *restrict, config_rule_t *restrict);
+
+static token_t *tok_new(const char *, const char *, const char *, size_t,
+    size_t);
+static void tok_free(token_t *);
+static void tok_log(token_t *restrict, bunyan_logger_t *restrict,
+     bunyan_level_t, const char *restrict, const char *restrict);
+static void tok_error(token_t *restrict, bunyan_logger_t *restrict,
+    const char *restrict, const char *restrict);
+static void tok_invalid(token_t *restrict, bunyan_logger_t *restrict,
+    keyword_t);
+
+static boolean_t parse_rule(input_cursor_t *restrict, const token_t *restrict,
+    config_rule_t **restrict);
+static boolean_t parse_address(input_cursor_t *restrict,
+    config_addr_t *restrict);
+
+static boolean_t parse_xform(input_cursor_t *restrict, config_xf_t **restrict);
+static boolean_t parse_encrbits(input_cursor_t *restrict,
+    config_xf_t *restrict);
+
+static boolean_t parse_kw(const char *restrict, keyword_t *restrict);
+static boolean_t parse_auth(const char *restrict, ikev2_auth_type_t *restrict);
+static boolean_t parse_authalg(const char *restrict, ikev2_xf_auth_t *restrict);
+static boolean_t parse_encralg(const char *restrict, ikev2_xf_encr_t *restrict);
+static boolean_t parse_p1_id(const char *restrict, config_auth_id_t *restrict);
+static boolean_t parse_p2_pfs(const char *restrict, ikev2_dh_t *restrict);
+static boolean_t parse_ip(const char *restrict, in_addr_t *restrict);
+static boolean_t parse_ip6(const char *restrict, in6_addr_t *restrict);
+static boolean_t parse_int(const char *restrict, uint64_t *restrict);
+static boolean_t parse_fp(const char *restrict, double *restrict);
+
+static input_t *input_new(FILE *restrict, bunyan_logger_t *restrict);
+static void input_free(input_t *);
+
+static void input_cursor_init(input_cursor_t *, input_t *, bunyan_logger_t *);
+static void input_cursor_fini(input_cursor_t *);
+static token_t *input_token(input_cursor_t *, boolean_t);
+static const token_t *input_peek(input_cursor_t *, boolean_t);
+static token_t *input_next_token(input_cursor_t *, boolean_t);
+static void input_cursor_getpos(input_cursor_t *restrict, const char *restrict,
+    const char **restrict, uint32_t *restrict, uint32_t *restrict);
+
+static boolean_t issep(char c, boolean_t);
+
+/*
+ * When processing a configuration file, we first load the entire contents
+ * into memory before doing any parsing.  This is to hopefully allow more
+ * contextual error messages (such as being able to output the full line of
+ * text where an error occurs, as well as the location where the error occurs).
+ * Once successfully parsed, the contents are discarded.
+ *
+ * The general approach is to then generate a stream of string tokens.  We
+ * defer interpretation of the tokens (e.g. 'IP address') since there are
+ * some instances where it'd be complicated to do so due to potential
+ * ambiguities.  Instead it's simpler to wait until there's more context.
+ *
+ * For example, once the 'local_addr' keyword has been seen, we know the next
+ * token should be either an IPV4 or IPV6 address, an IPV[46] address prefix
+ * (address/masklen), or an IPV[46] range (start address-end address).  We can
+ * attempt to convert the string accordingly without ambiguity.
+ *
+ * To assist in that, there is a (currently) limited ability to peek (view
+ * without advancing the stream) at the next token.  This has (so far)
+ * proven sufficient.
+ *
+ * To check the configuration, we build a new copy of config_t, and if it
+ * succeeds to completion, we know the configuration does not have any
+ * errors, and then discard it (instead of replacing the current configuration).
+ *
+ * TODO: We should probably support the ability to add and remove individual
+ * rules.
+ */
+void
+process_config(FILE *f, boolean_t check_only, bunyan_logger_t *blog)
+{
+	input_t *in = input_new(f, blog);
+	token_t *t = NULL, *targ = NULL;
+	config_t *cfg = NULL; 
+	config_xf_t *xf = NULL;
+	input_cursor_t ic = { 0 };
+	union {
+		uint64_t	ui;
+		double		d;
+	} val;
+
+	if (in == NULL) {
+		STDERR(error, blog, "failure reading input");
+		return;
+	}
+
+	cfg = calloc(1, sizeof (*cfg));
+	VERIFY3P(cfg, !=, NULL);
+
+	input_cursor_init(&ic, in, blog);
+	while ((t = input_token(&ic, B_TRUE)) != NULL) {
+		keyword_t kw;
+
+		if (strcmp(t->t_str, "{") == 0) {
+			config_rule_t *rule = NULL;
+
+			if (!parse_rule(&ic, t, &rule))
+				goto fail;
+
+			add_rule(cfg, rule);
+			tok_free(t);
+			continue;
+		}
+
+		if (!parse_kw(t->t_str, &kw)) {
+			tok_error(t, blog,
+			    "Unrecognized configuration parameter",
+			    "parameter");
+			goto fail;
+		}
+
+		VERIFY3S(kw, !=, KW_NONE);
+		VERIFY3S(kw, !=, KW_MAX);
+
+		if (keyword_tab[kw].kw_has_arg) {
+			targ = input_token(&ic, keyword_tab[kw].kw_minus);
+			if (targ == NULL) {
+				tok_error(t, blog,
+				    "Parameter is missing argument",
+				    "parameter");
+				goto fail;
+			}
+		}
+
+		switch (kw) {
+		case KW_NONE:
+		case KW_MAX:
+			INVALID("t->t_val.t_kw");
+			break;
+		case KW_CERT_ROOT:
+			add_str(&cfg->cfg_cert_root, &cfg->cfg_cert_root_alloc,
+			    targ->t_str);
+			break;
+		case KW_CERT_TRUST:
+			add_str(&cfg->cfg_cert_trust,
+			    &cfg->cfg_cert_trust_alloc, targ->t_str);
+			break;
+		case KW_EXPIRE_TIMER:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(t, blog, KW_EXPIRE_TIMER);
+				goto fail;
+			}
+			cfg->cfg_expire_timer = val.ui * NANOSEC;
+			break;
+		case KW_IGNORE_CRLS:
+			cfg->cfg_ignore_crls = B_TRUE;
+			break;
+		case KW_LDAP_SERVER:
+		case KW_PKCS11_PATH:
+			tok_log(t, blog, BUNYAN_L_INFO,
+			    "Ignoring deprecated configuration parameter",
+			    "parameter");
+			break;
+		case KW_RETRY_LIMIT:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(t, blog, KW_RETRY_LIMIT);
+				goto fail;
+			}
+			cfg->cfg_retry_limit = val.ui;
+			break;
+		case KW_PROXY:
+			cfg->cfg_proxy = strdup(targ->t_str);
+			VERIFY3P(cfg->cfg_proxy, !=, NULL);
+			break;
+		case KW_SOCKS:
+			cfg->cfg_socks = strdup(targ->t_str);
+			VERIFY3P(cfg->cfg_socks, !=, NULL);
+			break;
+		case KW_RETRY_TIMER_INIT:
+			if (parse_int(targ->t_str, &val.ui)) {
+				cfg->cfg_retry_init = val.ui * NANOSEC;
+			} else if (parse_fp(targ->t_str, &val.d)) {
+				cfg->cfg_retry_init =
+				    (hrtime_t)(val.d * NANOSEC);
+			} else {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			break;
+		case KW_RETRY_TIMER_MAX:
+			if (parse_int(targ->t_str, &val.ui)) {
+				cfg->cfg_retry_max = val.ui * NANOSEC;
+			} else if (parse_fp(targ->t_str, &val.d)) {
+				cfg->cfg_retry_max =
+				    (hrtime_t)(val.d * NANOSEC);
+			} else {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			break;
+		case KW_P1_LIFETIME_SECS:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			cfg->cfg_p1_lifetime_secs = val.ui;
+			break;
+		case KW_P1_NONCE_LEN:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p1_nonce_len = val.ui;
+			break;
+		case KW_P2_LIFETIME_SECS:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_lifetime_secs = val.ui;
+			break;
+		case KW_P2_SOFTLIFE_SECS:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_softlife_secs = val.ui;
+			break;
+		case KW_P2_IDLETIME_SECS:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_idletime_secs = val.ui;
+			break;
+		case KW_P2_LIFETIME_KB:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_lifetime_kb = val.ui;
+			break;
+		case KW_P2_SOFTLIFE_KB:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_softlife_kb = val.ui;
+			break;
+		case KW_P2_NONCE_LEN:
+			if (!parse_int(targ->t_str, &val.ui)) {
+				tok_invalid(targ, blog, kw);
+				goto fail;
+			}
+			/* XXX: check size */
+			cfg->cfg_p2_nonce_len = val.ui;
+			break;
+		case KW_LOCAL_ID_TYPE:
+			tok_log(t, blog, BUNYAN_L_INFO, "Unimplemented "
+			    "configuration parameter", "keyword");
+			break;
+		case KW_USE_HTTP:
+			cfg->cfg_use_http = B_TRUE;
+			break;
+		case KW_P2_PFS:
+			if (!parse_p2_pfs(targ->t_str, &cfg->cfg_p2_pfs)) {
+				tok_error(targ, blog, "Invalid p2_pfs value",
+				    "value");
+				goto fail;
+			}
+			break;
+		case KW_P1_XFORM:
+			if (!parse_xform(&ic, &xf))
+				goto fail;
+			add_xf(cfg, xf, B_FALSE);
+			xf = NULL;
+			break;
+		case KW_AUTH_METHOD:
+		case KW_OAKLEY_GROUP:
+		case KW_AUTH_ALG:
+		case KW_ENCR_ALG:
+			tok_error(t, blog, "Configuration parameter cannot be "
+			    "used outside of a transform definition",
+			    "parameter");
+			goto fail;
+		case KW_LABEL:
+		case KW_LOCAL_ADDR:
+		case KW_REMOTE_ADDR:
+		case KW_LOCAL_ID:
+		case KW_REMOTE_ID:
+			tok_error(t, blog, "Configuration parameter cannot be "
+			    "used outside of a rule definition", "parameter");
+			goto fail;
+		}
+
+		tok_free(t);
+		tok_free(targ);
+		t = NULL;
+		targ = NULL;
+	}
+
+	tok_free(t);
+	tok_free(targ);
+	input_cursor_fini(&ic);
+	input_free(in);
+
+	if (check_only) {
+		cfg_free(cfg);
+	} else {
+		config_t *old = NULL;
+
+		cfg->cfg_refcnt = 1;
+
+		PTH(pthread_rwlock_wrlock(&cfg_lock));
+		old = config;
+		config = cfg;
+		PTH(pthread_rwlock_unlock(&cfg_lock));
+		CONFIG_REFRELE(old);
+	}
+	return;
+
+fail:
+	tok_free(t);
+	tok_free(targ);
+	input_cursor_fini(&ic);
+	input_free(in);
+	cfg_free(cfg);
+}
+
+static boolean_t
+parse_xform(input_cursor_t *restrict ic, config_xf_t **restrict xfp)
+{
+	config_xf_t *xf = NULL;
+	token_t *t = NULL, *targ = NULL;
+	boolean_t seen_authalg = B_FALSE;
+	boolean_t seen_encralg = B_FALSE;
+	boolean_t seen_dh = B_FALSE;
+	boolean_t seen_authmethod = B_FALSE;
+	boolean_t seen_lifetime_secs = B_FALSE;
+	boolean_t seen_nonce_len = B_FALSE;
+	uint64_t val = 0;
+
+	xf = calloc(1, sizeof (*xf));
+	VERIFY3P(xf, !=, NULL);
+
+	if ((t = input_token(ic, B_FALSE)) == NULL) {
+		bunyan_error(ic->ic_log, "Unexpected end of input processing "
+		    "transform", BUNYAN_T_END);
+		goto fail;
+	}
+
+	if (strcmp(t->t_str, "{") != 0) {
+		bunyan_error(ic->ic_log, "Expected '{' after p1_xform",
+		    BUNYAN_T_STRING, "string", t->t_str,
+		    BUNYAN_T_END);
+		goto fail;
+	}
+
+	/*CONSTCOND*/
+	while (1) {
+		if ((t = input_token(ic, B_FALSE)) == NULL) {
+			bunyan_error(ic->ic_log,
+			    "Unexpected end of input processing transform",
+			    BUNYAN_T_END);
+			goto fail;
+		}
+		if (strcmp(t->t_str, "}") == 0)
+			break;
+
+		/* All of the keywords require an argument */
+		if ((targ = input_token(ic, B_FALSE)) == NULL) {
+			tok_error(t, ic->ic_log,
+			    "Missing argument to parameter", "parameter");
+			goto fail;
+		}
+
+		keyword_t kw = KW_NONE;
+
+		if (!parse_kw(t->t_str, &kw)) {
+			tok_error(t, ic->ic_log,
+			    "Unknown configuration parameter", "parameter");
+			goto fail;
+		}
+
+		switch (kw) {
+		case KW_AUTH_METHOD:
+			if (seen_authmethod)
+				goto duplicate;
+			if (!parse_auth(targ->t_str, &xf->xf_authtype)) {
+				tok_error(targ, ic->ic_log,
+				    "Unknown authentication method",
+				    "authmethod");
+				goto fail;
+			}
+			seen_authmethod = B_TRUE;
+			break;
+		case KW_OAKLEY_GROUP:
+			if (seen_dh)
+				goto duplicate;
+			if (!parse_int(targ->t_str, &val)) {
+				tok_error(targ, ic->ic_log,
+				    "Unknown oakley (DH) group",
+				    "group");
+				goto fail;
+			}
+			/* XXX: Should have a way to validate the value */
+			seen_dh = B_TRUE;
+			xf->xf_dh = (ikev2_dh_t)val;
+			break;
+		case KW_AUTH_ALG:
+			if (seen_authalg)
+				goto duplicate;
+			if (!parse_authalg(targ->t_str, &xf->xf_auth)) {
+				tok_error(targ, ic->ic_log,
+				    "Unknown authentication algorithm",
+				    "algorithm");
+				goto fail;
+			}
+			seen_authalg = B_TRUE;
+			break;
+		case KW_ENCR_ALG:
+			if (seen_encralg)
+				goto duplicate;
+			if (!parse_encralg(targ->t_str, &xf->xf_encr)) {
+				tok_error(targ, ic->ic_log,
+				    "Unknown encryption algorithm",
+				    "algorithm");
+				goto fail;
+			}
+			seen_encralg = B_TRUE;
+			if (!parse_encrbits(ic, xf))
+				goto fail;
+			break;
+		case KW_P1_LIFETIME_SECS:
+			if (seen_lifetime_secs)
+				goto duplicate;
+			if (!parse_int(targ->t_str, &val)) {
+				tok_error(targ, ic->ic_log, "Invalid value",
+				    "value");
+				goto fail;
+			}
+			xf->xf_lifetime_secs = (uint32_t)val;
+			seen_lifetime_secs = B_TRUE;
+			break;
+		case KW_P1_NONCE_LEN: /*xf_nonce_len*/
+			if (seen_nonce_len)
+				goto duplicate;
+			if (!parse_int(targ->t_str, &val)) {
+				tok_error(targ, ic->ic_log, "Invalid value",
+				    "value");
+				goto fail;
+			}
+			/* XXX: validate length */
+			xf->xf_nonce_len = (uint32_t)val;
+			seen_nonce_len = B_TRUE;
+			break;
+		default:
+			bunyan_error(ic->ic_log, "Parameter keyword not "
+			    "allowed in transform definition",
+			    BUNYAN_T_STRING, "keyword", t->t_str,
+			    BUNYAN_T_END);
+			goto fail;
+		}
+
+		tok_free(t);
+		tok_free(targ);
+		t = NULL;
+		targ = NULL;
+	}
+	*xfp = xf;
+	return (B_TRUE);
+
+duplicate:
+	tok_error(t, ic->ic_log, "Duplicate configuration parameter",
+	    "parameter");
+
+fail:
+	tok_free(t);
+	tok_free(targ);
+	free(xf);
+	*xfp = NULL;
+	return (B_FALSE);	
+}
+
+static boolean_t
+parse_encrbits(input_cursor_t *restrict ic, config_xf_t *restrict xf)
+{
+	const token_t *tpeek = NULL;
+	token_t *t = NULL;
+	uint64_t val = 0;
+
+	if ((tpeek = input_peek(ic, B_FALSE)) == NULL)
+		goto truncated;
+
+	/* No key length given, that's ok */
+	if (strcmp(tpeek->t_str, "(") != 0)
+		return (B_TRUE);
+
+	/* consume '(' */
+	tok_free(input_token(ic, B_FALSE));
+
+	if ((t = input_token(ic, B_FALSE)) == NULL)
+		goto truncated;
+
+	if (!parse_int(t->t_str, &val))
+		goto invalid;
+	if (val > SIZE_MAX)
+		goto toobig;
+	xf->xf_minbits = (size_t)val;
+	tok_free(t);
+
+	if ((t = input_token(ic, B_FALSE)) == NULL)
+		goto truncated;
+	if (strcmp(t->t_str, ")") == 0) {
+		xf->xf_maxbits = xf->xf_minbits;
+		goto done;
+	}
+
+	if (strcmp(t->t_str, "..") != 0)
+		goto unexpected;
+	tok_free(t);
+
+	if ((t = input_token(ic, B_TRUE)) == NULL)
+		goto truncated;
+	if (!parse_int(t->t_str, &val))
+		goto invalid;
+	if (val > SIZE_MAX)
+		goto toobig;
+	xf->xf_maxbits = val;
+
+	if (xf->xf_maxbits < xf->xf_minbits) {
+		bunyan_error(ic->ic_log,
+		    "Maximum keysize is smaller than minimum keysize",
+		    BUNYAN_T_STRING, "value", t->t_str,
+		    BUNYAN_T_UINT32, "line", t->t_line,
+		    BUNYAN_T_UINT32, "col", t->t_col,
+		    BUNYAN_T_END);
+		tok_free(t);
+		return (B_FALSE);
+	}
+
+	tok_free(t);
+	if ((t = input_token(ic, B_TRUE)) == NULL)
+		goto truncated;
+	if (strcmp(t->t_str, ")") != 0)
+		goto unexpected;
+
+done:
+	tok_free(t);
+	return (B_TRUE);
+
+unexpected:
+	bunyan_error(ic->ic_log, "Unexpected value after key length",
+	    BUNYAN_T_STRING, "value", t->t_str,
+	    BUNYAN_T_UINT32, "line", t->t_line,
+	    BUNYAN_T_UINT32, "col", t->t_col,
+	    BUNYAN_T_END);
+	tok_free(t);
+	return (B_FALSE);
+
+invalid:
+	bunyan_error(ic->ic_log, "Invalid key bitlength",
+	    BUNYAN_T_STRING, "bitlength", t->t_str,
+	    BUNYAN_T_UINT32, "line", t->t_line,
+	    BUNYAN_T_UINT32, "col", t->t_col,
+	    BUNYAN_T_END);
+	tok_free(t);
+	return (B_FALSE);
+
+toobig:
+	bunyan_error(ic->ic_log, "Keysize is too large",
+	    BUNYAN_T_UINT64, "keysize", val,
+	    BUNYAN_T_UINT32, "line", t->t_line,
+	    BUNYAN_T_UINT32, "col", t->t_col,
+	    BUNYAN_T_END);
+	tok_free(t);
+	return (B_FALSE);
+
+truncated:
+	tok_free(t);
+	bunyan_error(ic->ic_log, "Truncated input while reading transform",
+	    BUNYAN_T_END);
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_rule(input_cursor_t *restrict ic, const token_t *start,
+    config_rule_t **restrict rulep)
+{
+	token_t *t = NULL, *targ = NULL;
+	config_rule_t *rule = NULL;
+	config_xf_t *xf = NULL;
+	config_addr_t addr = { 0 };
+	boolean_t seen_label = B_FALSE;
+	boolean_t seen_local_addr = B_FALSE;
+	boolean_t seen_remote_addr = B_FALSE;
+	boolean_t seen_local_id_type = B_FALSE;
+	boolean_t seen_local_id = B_FALSE;
+	boolean_t seen_remote_id = B_FALSE;
+	boolean_t seen_p2_lifetime_secs = B_FALSE;
+	boolean_t seen_p2_pfs = B_FALSE;
+	boolean_t seen_p1_xform = B_FALSE;
+	boolean_t has_non_preshared = B_FALSE;
+
+	*rulep = NULL;
+
+	rule = calloc(1, sizeof (*rule));
+	VERIFY3P(rule, !=, NULL);
+
+	while ((t = input_token(ic, B_FALSE)) != NULL) {
+		keyword_t kw = KW_NONE;
+
+		if (strcmp(t->t_str, "}") == 0)
+			break;
+
+		if (!parse_kw(t->t_str, &kw)) {
+			tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+			    "Unrecognized configuration parameter",
+			    "parameter");
+			goto fail;
+		}
+
+		switch (kw) {
+		case KW_LOCAL_ADDR:
+		case KW_REMOTE_ADDR:
+		case KW_P1_XFORM:
+			break;
+		default:
+			targ = input_token(ic, B_FALSE);
+			if (targ == NULL) {
+				bunyan_error(ic->ic_log, "Input truncated "
+				    "while reading rule", BUNYAN_T_END);
+				goto fail;
+			}
+		}
+
+		switch (kw) {
+		case KW_LABEL:
+			if (seen_label)
+				goto duplicate;
+			rule->rule_label = strdup(targ->t_str);
+			if (rule->rule_label == NULL)
+				goto fail;
+			seen_label = B_TRUE;
+			break;
+		case KW_P2_PFS:
+			if (seen_p2_pfs)
+				goto duplicate;
+			if (!parse_p2_pfs(targ->t_str, &rule->rule_p2_dh)) {
+				tok_invalid(targ, ic->ic_log, KW_P2_PFS);
+				goto fail;
+			}
+			break;
+		case KW_P1_XFORM:
+			if (!parse_xform(ic, &xf))
+				goto fail;
+
+			add_xf(rule, xf, B_TRUE);
+			if (xf->xf_authtype != IKEV2_AUTH_SHARED_KEY_MIC)
+				has_non_preshared = B_TRUE;
+			seen_p1_xform = B_TRUE;
+			xf = NULL;
+			break;
+		case KW_LOCAL_ADDR:
+			(void) memset(&addr, 0, sizeof (addr));
+			if (!parse_address(ic, &addr))
+				goto fail;
+			seen_local_addr = B_TRUE;
+			break;
+		case KW_REMOTE_ADDR:
+			(void) memset(&addr, 0, sizeof (addr));
+			if (!parse_address(ic, &addr))
+				goto fail;
+			seen_remote_addr = B_TRUE;
+			break;
+		case KW_LOCAL_ID:
+			/*
+			 * According to the man page, only one ID is used
+			 * per rule, but instead of erroring, it just uses
+			 * the first one.
+			 */
+			if (seen_local_id)
+				break;
+			rule->rule_local_id = strdup(targ->t_str);
+			if (rule->rule_local_id == NULL)
+				goto fail;
+			seen_local_id = B_TRUE;
+			break;
+		case KW_REMOTE_ID:
+			/* XXX: allow multiple remote ids */
+			/* See KW_LOCAL_ID above */
+			if (seen_remote_id)
+				break;
+			rule->rule_remote_id = strdup(targ->t_str);
+			if (rule->rule_remote_id == NULL)
+				goto fail;
+			seen_remote_id = B_TRUE;
+			break;
+		case KW_LOCAL_ID_TYPE:
+			if (seen_local_id_type)
+				goto duplicate;
+			if (!parse_p1_id(targ->t_str,
+			    &rule->rule_local_id_type)) {
+				tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+				    "Unable to parse local_id_type", "value");
+				goto fail;
+			}
+			seen_local_id_type = B_TRUE;
+			break;
+		default:
+			tok_log(t, ic->ic_log, BUNYAN_L_ERROR, "Configuration "
+			    "parameter is invalid inside a rule definition",
+			    "parameter");
+			goto fail;
+		}
+
+		tok_free(t);
+		tok_free(targ);
+		t = NULL;
+		targ = NULL;
+	}
+
+	if (t == NULL) {
+		bunyan_error(ic->ic_log, "Input truncated while reading rule",
+		    BUNYAN_T_END);
+		goto fail;
+	}
+
+	/* Try to show as many errors as we can */
+	if (!seen_label)
+		tok_error(t, ic->ic_log, "Rule is missing a required label",
+		    NULL);
+	if (!seen_local_addr)
+		tok_error(t, ic->ic_log,
+		    "Rule is missing a required local address", NULL);
+	if (!seen_remote_addr)
+		tok_error(t, ic->ic_log,
+		    "Rule is missing a required remote address", NULL);
+
+	if (!seen_label || !seen_local_addr || !seen_remote_addr)
+		goto fail;
+
+	tok_free(t);
+	tok_free(targ);
+	*rulep = rule;
+	return (B_TRUE);
+
+duplicate:
+	tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+	    "Configuration parameter can only appear once in a transform "
+	    "definition", "parameter");
+
+fail:
+	tok_free(t);
+	tok_free(targ);
+	free(rule);
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_address(input_cursor_t *restrict ic, config_addr_t *restrict addrp)
+{
+	const token_t *tpeek = NULL;
+	token_t *t = NULL;
+	boolean_t ip6 = B_FALSE;
+	boolean_t ok = B_FALSE;
+
+	t = input_token(ic, B_TRUE);
+	if (t == NULL)
+		goto truncated;
+
+	if (!parse_ip(t->t_str, &addrp->cfa_startu.cfa_ip4)) {
+		if (!parse_ip6(t->t_str, &addrp->cfa_startu.cfa_ip6)) {
+			tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+			    "Unable to parse address", "address");
+			return (B_FALSE);
+		}
+		ip6 = B_TRUE;
+	}
+	tok_free(t);
+
+	tpeek = input_peek(ic, B_TRUE);
+	if (strcmp(tpeek->t_str, "-") == 0) {
+		/* consume - */
+		tok_free(input_token(ic, B_TRUE));
+
+		addrp->cfa_type =
+		    ip6 ? CFG_ADDR_IPV6_RANGE : CFG_ADDR_IPV4_RANGE;
+
+		t = input_token(ic, B_FALSE);
+		if (t == NULL)
+			goto truncated;
+
+		ok = ip6 ? parse_ip6(t->t_str, &addrp->cfa_endu.cfa_ip6) :
+		    parse_ip(t->t_str, &addrp->cfa_endu.cfa_ip4);
+		if (!ok) {
+			tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+			    "Unable to parse address", "address");
+		}
+		tok_free(t);
+		return (ok);
+	} else if (strcmp(tpeek->t_str, "/") == 0) {
+		uint64_t val = 0;
+
+		addrp->cfa_type =
+		    ip6 ? CFG_ADDR_IPV6_PREFIX : CFG_ADDR_IPV4_PREFIX;
+
+		/* consume "/" */
+		tok_free(input_token(ic, B_TRUE));
+
+		t = input_token(ic, B_FALSE);
+		if (t == NULL)
+			goto truncated;
+
+		if (!parse_int(t->t_str, &val)) {
+			tok_log(t, ic->ic_log, BUNYAN_L_ERROR,
+			    "Cannot parse mask length", "mask_len");
+			return (B_FALSE);
+		}
+		tok_free(t);
+		t = NULL;
+		if ((ip6 && val > 128) || (!ip6 && val > 32)) {
+			bunyan_error(ic->ic_log, "Mask length too long",
+			    BUNYAN_T_UINT64, "mask", val,
+			    BUNYAN_T_END);
+			return (B_FALSE);
+		}
+		addrp->cfa_endu.cfa_num = (uint8_t)val;
+		return (B_TRUE);
+	} else {
+		addrp->cfa_type =
+		    ip6 ? CFG_ADDR_IPV6 : CFG_ADDR_IPV4;
+	}
+
+	return (B_TRUE);
+
+truncated:
+	bunyan_error(ic->ic_log, "Input truncated while parsing address",
+	     BUNYAN_T_END);
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_p2_pfs(const char *restrict str, ikev2_dh_t *dhp)
+{
+	uint64_t val = 0;
+
+	if (!parse_int(str, &val))
+		return (B_FALSE);
+
+	/* XXX: validate value */
+
+	if (dhp != NULL)
+		*dhp = (int)val;
+	return (B_TRUE);
+}
+
+static boolean_t
+parse_ip(const char *restrict str, in_addr_t *restrict addrp)
+{
+	if (inet_pton(AF_INET, str, addrp) != 1)
+		return (B_FALSE);
+	return (B_TRUE);
+}
+
+static boolean_t
+parse_ip6(const char *restrict str, in6_addr_t *restrict addrp)
+{
+	if (inet_pton(AF_INET6, str, addrp) != 1)
+		return (B_FALSE);
+	return (B_TRUE);
+}
+
+static boolean_t
+parse_int(const char *restrict str, uint64_t *restrict intp)
+{
+	errno = 0;
+	*intp = strtoull(str, NULL, 0);
+	return ((errno == 0) ? B_TRUE : B_FALSE);
+}
+
+static boolean_t
+parse_fp(const char *restrict str, double *restrict dp)
+{
+	errno = 0;
+	*dp = strtod(str, NULL);
+	return ((errno == 0) ? B_TRUE : B_FALSE);
+}
+
+static boolean_t 
+parse_kw(const char *restrict str, keyword_t *restrict kwp)
+{
+	for (keyword_t kw = KW_NONE; kw < KW_MAX; kw++) {
+		if (strcmp(keyword_tab[kw].kw_str, str) == 0) {
+			*kwp = kw;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_auth(const char *restrict str, ikev2_auth_type_t *restrict authp)
+{
+	for (size_t i = 0; i < ARRAY_SIZE(auth_tab); i++) {
+		if (strcmp(auth_tab[i].a_str, str) == 0) {
+			*authp = auth_tab[i].a_id;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_authalg(const char *restrict str, ikev2_xf_auth_t *authp)
+{
+	for (size_t i = 0; i < ARRAY_SIZE(xf_auth_tab); i++) {
+		if (strcmp(xf_auth_tab[i].xfa_str, str) == 0) {
+			*authp = xf_auth_tab[i].xfa_id;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_encralg(const char *restrict str, ikev2_xf_encr_t *restrict encp)
+{
+	for (size_t i = 0; i < ARRAY_SIZE(xf_encr_tab); i++) {
+		if (strcmp(xf_encr_tab[i].xfe_str, str) == 0) {
+			*encp = xf_encr_tab[i].xfe_id;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+static boolean_t
+parse_p1_id(const char *restrict str, config_auth_id_t *restrict p1p)
+{
+	for (size_t i = 0; i < ARRAY_SIZE(p1_id_tab); i++) {
+		if (strcmp(p1_id_tab[i].p1_str, str) == 0) {
+			*p1p = p1_id_tab[i].p1_id;
+			return (B_TRUE);
+		}
+	}
+	return (B_FALSE);
+}
+
+static token_t *
+tok_new(const char *startp, const char *endp, const char *linep, size_t line,
+    size_t col)
+{
+	VERIFY3P(endp, >=, startp);
+
+	token_t *t = NULL;
+	size_t len = (size_t)(endp - startp) + 1;
+
+	t = calloc(1, sizeof (*t));
+	VERIFY3P(t, !=, NULL);
+
+	t->t_str = calloc(1, len);
+	VERIFY3P(t, !=, NULL);
+
+	(void) strlcpy(t->t_str, startp, len);
+	t->t_linep = linep;
+	t->t_line = line;
+	t->t_col = col;
+	return (t);
+}
+
+static void
+tok_free(token_t *t)
+{
+	if (t == NULL)
+		return;
+	free(t->t_str);
+	free(t);
+}
+
+#define	STR(x) case x: return (#x)
+static const char *
+cfg_auth_id_str(config_auth_id_t id)
+{
+	switch (id) {
+	STR(CFG_AUTH_ID_DN);
+	STR(CFG_AUTH_ID_DNS);
+	STR(CFG_AUTH_ID_GN);
+	STR(CFG_AUTH_ID_IPV4);
+	STR(CFG_AUTH_ID_IPV4_PREFIX);
+	STR(CFG_AUTH_ID_IPV4_RANGE);
+	STR(CFG_AUTH_ID_IPV6);
+	STR(CFG_AUTH_ID_IPV6_PREFIX);
+	STR(CFG_AUTH_ID_IPV6_RANGE);
+	STR(CFG_AUTH_ID_EMAIL);
+	}
+	return ("UNKNOWN");
+}
+#undef	STR
+
+static void
+tok_log(token_t *restrict t, bunyan_logger_t *restrict blog,
+    bunyan_level_t level, const char *msg, const char *strname)
+{
+	char *linecpy = NULL;
+	const char *endp = strchr(t->t_linep, '\n');
+	size_t len = 0;
+
+	if (endp != NULL)
+		len = endp - t->t_linep + 1;
+	else
+		len = strlen(t->t_linep) + 1;
+
+	linecpy = malloc(len);
+	VERIFY3P(linecpy, !=, NULL);
+	(void) strlcpy(linecpy, t->t_linep, len);
+
+	if (strname != NULL) {
+		getlog(level)(blog, msg, BUNYAN_T_STRING, strname, t->t_str,
+		    BUNYAN_T_STRING, "line", linecpy,
+		    BUNYAN_T_UINT32, "lineno", t->t_line,
+		    BUNYAN_T_UINT32, "col", t->t_col,
+		    BUNYAN_T_END);
+	} else {
+		getlog(level)(blog, msg,
+		    BUNYAN_T_STRING, "line", linecpy,
+		    BUNYAN_T_UINT32, "lineno", t->t_line,
+		    BUNYAN_T_UINT32, "col", t->t_col,
+		    BUNYAN_T_END);
+	}
+	free(linecpy);
+}
+
+static void
+tok_error(token_t *restrict t, bunyan_logger_t *restrict b,
+    const char *restrict msg, const char *restrict tname)
+{
+	tok_log(t, b, BUNYAN_L_ERROR, msg, tname);
+}
+
+static void
+tok_invalid(token_t *restrict t, bunyan_logger_t *restrict b, keyword_t kw)
+{
+	char buf[128] = { 0 };
+	(void) snprintf(buf, sizeof (buf), "Invalid %s parameter",
+	    keyword_tab[kw]);
+	tok_error(t, b, buf, "parameter");
+}
+
+static input_t *
+input_new(FILE *restrict f, bunyan_logger_t *restrict blog)
+{
+	input_t *in = NULL;
+	char *p = NULL;
+	ssize_t n = 0;
+	size_t cnt = 0;
+	size_t nlines = 0;
+	struct stat sb = { 0 };
+	int fd = -1;
+
+	in = calloc(1, sizeof (*in));
+	VERIFY3P(in, !=, NULL);
+
+	fd = fileno(f);
+	if (fstat(fd, &sb) == -1) {
+		STDERR(error, blog, "stat failed");
+		goto fail;
+	}
+
+	/*
+	 * Try to read in one go, however the input could be a pipe instead
+	 * of a file, in which case we have to keep growing the buffer
+	 * (up to the limit)
+	 */
+	if (S_ISREG(sb.st_mode)) {
+		in->in_buflen = sb.st_size + 2;
+	} else {
+		in->in_buflen = CONFIG_CHUNK;
+	}
+	in->in_buf = calloc(1, in->in_buflen);
+	VERIFY3P(in->in_buf, !=, NULL);
+
+	do {
+		n = fread(in->in_buf + cnt, 1, in->in_buflen - cnt - 1, f);
+		if (n < 0) {
+			STDERR(error, blog, "read failed");
+			goto fail;
+		}
+		cnt += n;
+
+		if (cnt + 1 >= in->in_buflen) {
+			if (in->in_buflen >= CONFIG_MAX) {
+				bunyan_error(blog, "Input size exceeds limits",
+				    BUNYAN_T_UINT32, "size",
+				    (uint32_t)in->in_buflen,
+				    BUNYAN_T_UINT32, "limit",
+				    (uint32_t)CONFIG_MAX,
+				    BUNYAN_T_END);
+				goto fail;
+			}
+
+			size_t newlen = P2ROUNDUP(in->in_buflen + CONFIG_CHUNK,
+			    CONFIG_CHUNK);
+
+			char *newp = realloc(in->in_buf, newlen);
+			VERIFY3P(newp, !=, NULL);
+
+			in->in_buf = newp;
+			in->in_buflen = newlen;
+		}
+	} while (n > 0);
+	in->in_buf[cnt] = '\0';
+
+	for (p = in->in_buf, nlines = 0; p != NULL; p = strchr(p + 1, '\n'))
+		nlines++;
+
+	in->in_lines = calloc(nlines + 1, sizeof (char *));
+	VERIFY3P(in->in_lines, !=, NULL);
+
+	for (p = in->in_buf, nlines = 0; p != NULL; p = strchr(p + 1, '\n'))
+		in->in_lines[nlines++] = p;
+
+	return (in);
+
+fail:
+	input_free(in);
+	return (NULL);
+}
+
+static token_t *
+input_token(input_cursor_t *ic, boolean_t minus_is_sep)
+{
+	token_t *t = NULL;
+	if (ic->ic_peek != NULL ) {
+		t = ic->ic_peek;
+		ic->ic_peek = NULL;
+	} else {
+		t = input_next_token(ic, minus_is_sep);
+	}
+
+	return (t);
+}
+
+/* NOTE: Results of input_peek() should NOT be freed */
+static const token_t *
+input_peek(input_cursor_t *ic, boolean_t minus_is_sep)
+{
+	if (ic->ic_peek != NULL)
+		return (ic->ic_peek);
+
+	ic->ic_peek = input_next_token(ic, minus_is_sep);
+	return (ic->ic_peek);
+}
+
+/*
+ * Actually get the next token from the input.  This is used both by
+ * input_token() and input_peek() and shouldn't be called by anything else.
+ */
+static token_t *
+input_next_token(input_cursor_t *ic, boolean_t minus_is_sep)
+{
+	char *start = NULL, *end = NULL;
+	const char *linep = NULL;
+	uint32_t line = 0, col = 0;
+
+	VERIFY3P(ic->ic_p, >=, ic->ic_input->in_buf);
+	VERIFY3P(ic->ic_p, <, ic->ic_input->in_buf + ic->ic_input->in_buflen);
+
+again:
+	while (*ic->ic_p != '\0' && isspace(*ic->ic_p))
+		ic->ic_p++;
+
+	if (*ic->ic_p == '#') {
+		/* skip to next line */
+		while (*ic->ic_p != '\0' && *ic->ic_p != '\n')
+			ic->ic_p++;
+		goto again;
+	}
+
+	if (*ic->ic_p == '\0')
+		return (NULL);
+
+	start = ic->ic_p;
+	end = start + 1;
+
+	/* If the first character is a separator, we're done */
+	if (issep(*start, minus_is_sep)) {
+		ic->ic_p = end;
+		goto done;
+	}
+
+	if (*start == '"') {
+		while (*end != '\0' && *end != '\n' && *end != '"')
+			end++;
+
+		if (*end != '"') {
+			input_cursor_getpos(ic, start, &linep, &line, &col);
+			bunyan_error(ic->ic_log, "Unterminated quoted string",
+			    BUNYAN_T_UINT32, "line", line,
+			    BUNYAN_T_UINT32, "col", col,
+			    BUNYAN_T_END);
+			return (NULL);
+		}
+
+		start++;
+		ic->ic_p = end + 1;
+		goto done;
+	}
+
+	while (*end != '\0' && !isspace(*end)) {
+		if (issep(*end, minus_is_sep) || isspace(*end))
+			break;
+		end++;
+	}
+	ic->ic_p = end;
+
+done:
+	input_cursor_getpos(ic, start, &linep, &line, &col);
+	return (tok_new(start, end, linep, line, col));
+}
+
+static void
+input_cursor_getpos(input_cursor_t *restrict ic, const char *restrict p,
+    const char **restrict linepp, uint32_t *restrict linep,
+    uint32_t *restrict colp)
+{
+	VERIFY3P(ic->ic_input->in_buf, <=, p);
+	VERIFY3P(ic->ic_input->in_buf + ic->ic_input->in_buflen, >, p);
+
+	char **lineidx = ic->ic_input->in_lines;
+	uint32_t line;
+	for (line = 1; lineidx[line] != NULL && lineidx[line] <= p; line++)
+		;
+
+	line--;
+	*linep = line;
+	*colp = (uint32_t)(p - lineidx[line]);
+	*linepp = lineidx[line];
+}
+
+static void
+input_cursor_init(input_cursor_t *restrict ic, input_t *restrict in,
+    bunyan_logger_t *blog)
+{
+	(void) memset(ic, 0, sizeof (*ic));
+	ic->ic_input = in;
+	ic->ic_p = in->in_buf;
+	ic->ic_log = blog;
+}
+
+static void
+input_cursor_fini(input_cursor_t *ic)
+{
+	free(ic->ic_peek);
+	(void) memset(ic, 0, sizeof (*ic));
+}
+
+static void
+input_free(input_t *in)
+{
+	if (in == NULL)
+		return;
+
+	free(in->in_buf);
+	free(in->in_lines);
+	free(in);
+}
+
+#define	CHUNK_SZ	(8)
+/*
+ * Append a string onto an array of strings.  Since these shouldn't be heavily
+ * called, we're not (currently at least) worried about the possibility
+ * of excessive realloc() calls.
+ */
+static void
+add_str(char ***restrict ppp, size_t *restrict allocp, const char *restrict str)
+{
+	char *newstr = NULL;
+	char **array = *ppp;
+	size_t nelems = 0;
+
+	while (nelems < *allocp && array[nelems] != NULL)
+		nelems++;
+
+	if (nelems + 2 > *allocp) {
+		char **newarray = NULL;
+		size_t newsize = *allocp + CHUNK_SZ;
+		size_t amt = newsize * sizeof (char *);
+
+		VERIFY3U(amt, >, newsize);
+		VERIFY3U(amt, >=, sizeof (char *));
+
+		/* realloc_array() would be nice */
+		newarray = realloc(array, amt);
+		VERIFY3P(newarray, !=, NULL);
+
+		*ppp = array = newarray;
+		*allocp = newsize;
+	}
+
+	newstr = strdup(str);
+	VERIFY3P(newstr, !=, NULL);
+
+	array[nelems++] = newstr;
+	array[nelems] = NULL;
+}
+
+static void
+add_xf(void *restrict ptr, config_xf_t *restrict xf, boolean_t ptr_is_rule)
+{
+	config_xf_t **xfp = NULL;
+	size_t cur = 0;
+	size_t nxf = 0;
+
+	if (ptr_is_rule) {
+		config_rule_t *crp = ptr;
+
+		cur = crp->rule_nxf;
+		xfp = crp->rule_xf;
+	} else {
+		config_t *cp = ptr;
+
+		cur = cp->cfg_xforms_alloc;
+		xfp = cp->cfg_xforms;
+	}
+
+	while (nxf < cur && xfp[nxf] != NULL)
+		nxf++;
+
+	if (nxf + 2 > cur) {
+		config_xf_t **newxf = NULL;
+		size_t newalloc = cur + CHUNK_SZ;
+		size_t amt = newalloc * sizeof (config_xf_t *);
+
+		VERIFY3U(amt, >, newalloc);
+		VERIFY3U(amt, >=, sizeof (config_xf_t *));
+
+		newxf = realloc(xfp, amt);
+		VERIFY3P(newxf, !=, NULL);
+
+		if (ptr_is_rule) {
+			config_rule_t *crp = ptr;
+
+			crp->rule_nxf = newalloc;
+			crp->rule_xf = xfp = newxf;
+		} else {
+			config_t *cp = ptr;
+
+			cp->cfg_xforms = xfp = newxf;
+			cp->cfg_xforms_alloc = newalloc;
+		}
+	}
+
+	xfp[nxf++] = xf;
+	xfp[nxf] = NULL;
+}
+
+static void
+add_rule(config_t *restrict cfg, config_rule_t *restrict rule)
+{
+	/* TODO: validate label value is unique */
+	size_t nrules = 0;
+
+	while (nrules < cfg->cfg_rules_alloc && cfg->cfg_rules[nrules] != NULL)
+		nrules++;
+
+	if (nrules + 2 > cfg->cfg_rules_alloc) {
+		config_rule_t **newrules = NULL;
+		size_t newalloc = cfg->cfg_rules_alloc + CHUNK_SZ;
+		size_t amt = newalloc * sizeof (config_rule_t *);
+
+		VERIFY3U(amt, >, newalloc);
+		VERIFY3U(amt, >=, sizeof (config_rule_t *));
+
+		newrules = realloc(cfg->cfg_rules, amt);
+		VERIFY3P(newrules, !=, NULL);
+
+		cfg->cfg_rules = newrules;
+		cfg->cfg_rules_alloc = newalloc;
+	}
+
+	rule->rule_config = cfg;
+	cfg->cfg_rules[nrules++] = rule;
+	cfg->cfg_rules[nrules] = NULL;
+}
+
+/* Is the given character a token separator? */
+static boolean_t
+issep(char c, boolean_t minus_is_sep)
+{
+	switch (c) {
+	case '{': case '}':
+	case '(': case ')':
+	case '/':
+		return (B_TRUE);
+	case '-':
+		if (minus_is_sep)
+			return (B_TRUE);
+		break;
+	}
+	return (B_FALSE);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/defs.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,230 @@
+
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#ifndef _DEFS_H
+#define	_DEFS_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/debug.h>
+#include <ikedoor.h>
+#include <cryptoutil.h>
+#include <security/cryptoki.h>
+#include <stdio.h>
+#include <assert.h>
+#include <umem.h>
+#include <bunyan.h>
+#include <libintl.h>
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+#ifndef SOCKADDR_U_T
+#define	SOCKADDR_U_T
+typedef union sockaddr_u_s {
+	struct sockaddr_storage *sau_ss;
+	struct sockaddr_in	*sau_sin;
+	struct sockaddr_in6	*sau_sin6;
+} sockaddr_u_t;
+#endif /* SOCKADDR_U_T */
+
+/* Parsed-out PF_KEY message. */
+typedef struct parsedmsg_s {
+	struct parsedmsg_s *pmsg_next;
+	sadb_msg_t *pmsg_samsg;
+	sadb_ext_t *pmsg_exts[SADB_EXT_MAX + 2]; /* 2 for alignment */
+	sockaddr_u_t pmsg_sau;
+	sockaddr_u_t pmsg_dau;
+	sockaddr_u_t pmsg_isau;
+	sockaddr_u_t pmsg_idau;
+	sockaddr_u_t pmsg_nlau;
+	sockaddr_u_t pmsg_nrau;
+} parsedmsg_t;
+
+#define	pmsg_sss pmsg_sau.sau_ss
+#define	pmsg_ssin pmsg_sau.sau_sin
+#define	pmsg_ssin6 pmsg_sau.sau_sin6
+#define	pmsg_dss pmsg_dau.sau_ss
+#define	pmsg_dsin pmsg_dau.sau_sin
+#define	pmsg_dsin6 pmsg_dau.sau_sin6
+#define	pmsg_isss pmsg_isau.sau_ss
+#define	pmsg_issin pmsg_isau.sau_sin
+#define	pmsg_issin6 pmsg_isau.sau_sin6
+#define	pmsg_idss pmsg_idau.sau_ss
+#define	pmsg_idsin pmsg_idau.sau_sin
+#define	pmsg_idsin6 pmsg_idau.sau_sin6
+#define	pmsg_nlss pmsg_nlau.sau_ss
+#define	pmsg_nlsin pmsg_nlau.sau_sin
+#define	pmsg_nlsin6 pmsg_nlau.sau_sin6
+#define	pmsg_nrss pmsg_nrau.sau_ss
+#define	pmsg_nrsin pmsg_rnau.sau_sin
+#define	pmsg_nrsin6 pmsg_nrau.sau_sin6
+
+typedef void (*pfreq_cb_t)(sadb_msg_t *, void *);
+typedef struct algindex {
+	const char *desc;
+	int doi_num;
+} algindex_t;
+
+/*
+ * Compare two AF_INET{,6} sockaddrs (no port).  Assume sockaddr_storage
+ * pointers are passed, and also verifies the address families match and
+ * are either AF_INET or AF_INET6.
+ */
+#define	SA_ADDR_EQ(sa1, sa2)						\
+	(((sa1)->ss_family == (sa2)->ss_family) &&			\
+	    ((((sa1)->ss_family == AF_INET) &&				\
+		((struct sockaddr_in *)(sa1))->sin_addr.s_addr ==	\
+		((struct sockaddr_in *)(sa2))->sin_addr.s_addr) ||	\
+		(((sa1)->ss_family == AF_INET6) &&			\
+		IN6_ARE_ADDR_EQUAL(&((struct sockaddr_in6 *)(sa1))->sin6_addr,\
+		    &((struct sockaddr_in6 *)(sa2))->sin6_addr))))
+
+/*
+ * Compare two AF_INET{,6} sockaddr ports.  Exploit the identical offsets for
+ * sin_port/sin6_port.  (Does not check sockaddr families a priori.)
+ */
+#define	SA_PORT_EQ(sa1, sa2) (((struct sockaddr_in *)(sa1))->sin_port == \
+	    ((struct sockaddr_in *)(sa2))->sin_port)
+
+/*
+ * Compare two AF_INET{,6} sockaddrs (including ports).  Exploit the
+ * identical offsets for sin_port/sin6_port.
+ */
+#define	SA_FULL_EQ(sa1, sa2) (SA_ADDR_EQ(sa1, sa2) && SA_PORT_EQ(sa1, sa2))
+
+#define	INVALID(var) assfail("Invalid value of " # var, __FILE__, __LINE__)
+#define	ARRAY_SIZE(x) (sizeof (x) / sizeof (x[0]))
+
+/*
+ * Simple wrapper for pthread calls that should never fail under
+ * normal conditions.
+ */
+#define	PTH(fn) do {							\
+	int __pthread_rc = fn;						\
+	if (__pthread_rc < 0)						\
+		assfail(#fn " call failed", __FILE__, __LINE__);	\
+_NOTE(CONSTCOND) } while (0)
+
+/* BEGIN CSTYLED */
+#define	STDERR(_lvl, _log, _msg, ...)			\
+	(void) bunyan_##_lvl((_log), (_msg),		\
+	BUNYAN_T_STRING, "err", strerror(errno),	\
+	BUNYAN_T_INT32, "errno", (int32_t)(errno),	\
+	BUNYAN_T_STRING, "func", __func__,		\
+	BUNYAN_T_STRING, "file", __FILE__,		\
+	BUNYAN_T_INT32, "line", __LINE__,		\
+	## __VA_ARGS__,					\
+	BUNYAN_T_END)
+/* END CSTYLED */
+
+static inline uint32_t
+ss_port(const struct sockaddr_storage *ss)
+{
+	sockaddr_u_t sau;
+	sau.sau_ss = (struct sockaddr_storage *)ss;
+	switch (ss->ss_family) {
+	case AF_INET:
+		return ((uint32_t)sau.sau_sin->sin_port);
+	case AF_INET6:
+		return ((uint32_t)sau.sau_sin6->sin6_port);
+	default:
+		INVALID("ss->ss_family");
+		/*NOTREACHED*/
+		return (NULL);
+	}
+}
+
+static inline const void *
+ss_addr(const struct sockaddr_storage *ss)
+{
+	sockaddr_u_t sau;
+	sau.sau_ss = (struct sockaddr_storage *)ss;
+	switch (ss->ss_family) {
+	case AF_INET:
+		return (&sau.sau_sin->sin_addr);
+	case AF_INET6:
+		return (&sau.sau_sin6->sin6_addr);
+	default:
+		INVALID("ss->ss_family");
+		/*NOTREACHED*/
+		return (NULL);
+	}
+}
+
+static inline int
+ss_bunyan(const struct sockaddr_storage *ss)
+{
+	switch (ss->ss_family) {
+	case AF_INET:
+		return (BUNYAN_T_IP);
+	case AF_INET6:
+		return (BUNYAN_T_IP6);
+	default:
+		INVALID("ss->ss_family");
+		/*NOTREACHED*/
+		return (BUNYAN_T_END);
+	}
+}
+
+/* BEGIN CSTYLED */
+#define	NETLOG(_level, _log, _msg, _src, _dest, ...)	\
+	(void) bunyan_##_level((_log), (_msg),		\
+	BUNYAN_T_STRING, "func", __func__,		\
+	BUNYAN_T_STRING, "file", __FILE__,		\
+	BUNYAN_T_INT32, "line", __LINE__,		\
+	ss_bunyan(_src), "src", ss_addr(_src),		\
+	BUNYAN_T_UINT32, "srcport", ss_port(_src),	\
+	ss_bunyan(_dest), "dest", ss_addr(_dest),	\
+	BUNYAN_T_UINT32, "destport", ss_port(_dest),	\
+	## __VA_ARGS__,					\
+	BUNYAN_T_END)
+/* END CSTYLED */
+
+typedef enum event {
+	EVENT_NONE,
+	EVENT_SIGNAL
+} event_t;
+
+extern char *my_fmri;
+extern bunyan_logger_t *log;
+extern int port;
+
+void schedule_socket(int, void(*)(int, void *));
+
+typedef int (*bunyan_logfn_t)(bunyan_logger_t *, const char *, ...);
+bunyan_logfn_t getlog(bunyan_level_t);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* _DEFS_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/dh.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,393 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <security/cryptoki.h>
+#include "dh.h"
+#include "pkcs11.h"
+
+typedef struct {
+	int	id;
+	int	bits;
+	int	genbits;
+	uchar_t	*prime;
+	uchar_t	*generator;
+} dhgroup_t;
+
+
+static dhgroup_t dh_groups[] = {
+	{
+		.id = IKEV2_DH_MODP_768,
+		.bits = 768,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45"
+	},
+	{
+		.id = IKEV2_DH_MODP_1024,
+		.bits = 1024,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE6\x53\x81\xFF\xFF"
+		    "\xFF\xFF\xFF\xFF\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_1536,
+		.bits = 1536,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x23\x73\x27\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_2048,
+		.bits = 2048,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x18\x21\x7C\x32\x90\x5E\x46\x2E\x36\xCE\x3B"
+		    "\xE3\x9E\x77\x2C\x18\x0E\x86\x03\x9B\x27\x83\xA2\xEC\x07"
+		    "\xA2\x8F\xB5\xC5\x5D\xF0\x6F\x4C\x52\xC9\xDE\x2B\xCB\xF6"
+		    "\x95\x58\x17\x18\x39\x95\x49\x7C\xEA\x95\x6A\xE5\x15\xD2"
+		    "\x26\x18\x98\xFA\x05\x10\x15\x72\x8E\x5A\x8A\xAC\xAA\x68"
+		    "\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_3072,
+		.bits = 3072,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x18\x21\x7C\x32\x90\x5E\x46\x2E\x36\xCE\x3B"
+		    "\xE3\x9E\x77\x2C\x18\x0E\x86\x03\x9B\x27\x83\xA2\xEC\x07"
+		    "\xA2\x8F\xB5\xC5\x5D\xF0\x6F\x4C\x52\xC9\xDE\x2B\xCB\xF6"
+		    "\x95\x58\x17\x18\x39\x95\x49\x7C\xEA\x95\x6A\xE5\x15\xD2"
+		    "\x26\x18\x98\xFA\x05\x10\x15\x72\x8E\x5A\x8A\xAA\xC4\x2D"
+		    "\xAD\x33\x17\x0D\x04\x50\x7A\x33\xA8\x55\x21\xAB\xDF\x1C"
+		    "\xBA\x64\xEC\xFB\x85\x04\x58\xDB\xEF\x0A\x8A\xEA\x71\x57"
+		    "\x5D\x06\x0C\x7D\xB3\x97\x0F\x85\xA6\xE1\xE4\xC7\xAB\xF5"
+		    "\xAE\x8C\xDB\x09\x33\xD7\x1E\x8C\x94\xE0\x4A\x25\x61\x9D"
+		    "\xCE\xE3\xD2\x26\x1A\xD2\xEE\x6B\xF1\x2F\xFA\x06\xD9\x8A"
+		    "\x08\x64\xD8\x76\x02\x73\x3E\xC8\x6A\x64\x52\x1F\x2B\x18"
+		    "\x17\x7B\x20\x0C\xBB\xE1\x17\x57\x7A\x61\x5D\x6C\x77\x09"
+		    "\x88\xC0\xBA\xD9\x46\xE2\x08\xE2\x4F\xA0\x74\xE5\xAB\x31"
+		    "\x43\xDB\x5B\xFC\xE0\xFD\x10\x8E\x4B\x82\xD1\x20\xA9\x3A"
+		    "\xD2\xCA\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_4096,
+		.bits = 4096,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x18\x21\x7C\x32\x90\x5E\x46\x2E\x36\xCE\x3B"
+		    "\xE3\x9E\x77\x2C\x18\x0E\x86\x03\x9B\x27\x83\xA2\xEC\x07"
+		    "\xA2\x8F\xB5\xC5\x5D\xF0\x6F\x4C\x52\xC9\xDE\x2B\xCB\xF6"
+		    "\x95\x58\x17\x18\x39\x95\x49\x7C\xEA\x95\x6A\xE5\x15\xD2"
+		    "\x26\x18\x98\xFA\x05\x10\x15\x72\x8E\x5A\x8A\xAA\xC4\x2D"
+		    "\xAD\x33\x17\x0D\x04\x50\x7A\x33\xA8\x55\x21\xAB\xDF\x1C"
+		    "\xBA\x64\xEC\xFB\x85\x04\x58\xDB\xEF\x0A\x8A\xEA\x71\x57"
+		    "\x5D\x06\x0C\x7D\xB3\x97\x0F\x85\xA6\xE1\xE4\xC7\xAB\xF5"
+		    "\xAE\x8C\xDB\x09\x33\xD7\x1E\x8C\x94\xE0\x4A\x25\x61\x9D"
+		    "\xCE\xE3\xD2\x26\x1A\xD2\xEE\x6B\xF1\x2F\xFA\x06\xD9\x8A"
+		    "\x08\x64\xD8\x76\x02\x73\x3E\xC8\x6A\x64\x52\x1F\x2B\x18"
+		    "\x17\x7B\x20\x0C\xBB\xE1\x17\x57\x7A\x61\x5D\x6C\x77\x09"
+		    "\x88\xC0\xBA\xD9\x46\xE2\x08\xE2\x4F\xA0\x74\xE5\xAB\x31"
+		    "\x43\xDB\x5B\xFC\xE0\xFD\x10\x8E\x4B\x82\xD1\x20\xA9\x21"
+		    "\x08\x01\x1A\x72\x3C\x12\xA7\x87\xE6\xD7\x88\x71\x9A\x10"
+		    "\xBD\xBA\x5B\x26\x99\xC3\x27\x18\x6A\xF4\xE2\x3C\x1A\x94"
+		    "\x68\x34\xB6\x15\x0B\xDA\x25\x83\xE9\xCA\x2A\xD4\x4C\xE8"
+		    "\xDB\xBB\xC2\xDB\x04\xDE\x8E\xF9\x2E\x8E\xFC\x14\x1F\xBE"
+		    "\xCA\xA6\x28\x7C\x59\x47\x4E\x6B\xC0\x5D\x99\xB2\x96\x4F"
+		    "\xA0\x90\xC3\xA2\x23\x3B\xA1\x86\x51\x5B\xE7\xED\x1F\x61"
+		    "\x29\x70\xCE\xE2\xD7\xAF\xB8\x1B\xDD\x76\x21\x70\x48\x1C"
+		    "\xD0\x06\x91\x27\xD5\xB0\x5A\xA9\x93\xB4\xEA\x98\x8D\x8F"
+		    "\xDD\xC1\x86\xFF\xB7\xDC\x90\xA6\xC0\x8F\x4D\xF4\x35\xC9"
+		    "\x34\x06\x31\x99\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_6144,
+		.bits = 6144,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x18\x21\x7C\x32\x90\x5E\x46\x2E\x36\xCE\x3B"
+		    "\xE3\x9E\x77\x2C\x18\x0E\x86\x03\x9B\x27\x83\xA2\xEC\x07"
+		    "\xA2\x8F\xB5\xC5\x5D\xF0\x6F\x4C\x52\xC9\xDE\x2B\xCB\xF6"
+		    "\x95\x58\x17\x18\x39\x95\x49\x7C\xEA\x95\x6A\xE5\x15\xD2"
+		    "\x26\x18\x98\xFA\x05\x10\x15\x72\x8E\x5A\x8A\xAA\xC4\x2D"
+		    "\xAD\x33\x17\x0D\x04\x50\x7A\x33\xA8\x55\x21\xAB\xDF\x1C"
+		    "\xBA\x64\xEC\xFB\x85\x04\x58\xDB\xEF\x0A\x8A\xEA\x71\x57"
+		    "\x5D\x06\x0C\x7D\xB3\x97\x0F\x85\xA6\xE1\xE4\xC7\xAB\xF5"
+		    "\xAE\x8C\xDB\x09\x33\xD7\x1E\x8C\x94\xE0\x4A\x25\x61\x9D"
+		    "\xCE\xE3\xD2\x26\x1A\xD2\xEE\x6B\xF1\x2F\xFA\x06\xD9\x8A"
+		    "\x08\x64\xD8\x76\x02\x73\x3E\xC8\x6A\x64\x52\x1F\x2B\x18"
+		    "\x17\x7B\x20\x0C\xBB\xE1\x17\x57\x7A\x61\x5D\x6C\x77\x09"
+		    "\x88\xC0\xBA\xD9\x46\xE2\x08\xE2\x4F\xA0\x74\xE5\xAB\x31"
+		    "\x43\xDB\x5B\xFC\xE0\xFD\x10\x8E\x4B\x82\xD1\x20\xA9\x21"
+		    "\x08\x01\x1A\x72\x3C\x12\xA7\x87\xE6\xD7\x88\x71\x9A\x10"
+		    "\xBD\xBA\x5B\x26\x99\xC3\x27\x18\x6A\xF4\xE2\x3C\x1A\x94"
+		    "\x68\x34\xB6\x15\x0B\xDA\x25\x83\xE9\xCA\x2A\xD4\x4C\xE8"
+		    "\xDB\xBB\xC2\xDB\x04\xDE\x8E\xF9\x2E\x8E\xFC\x14\x1F\xBE"
+		    "\xCA\xA6\x28\x7C\x59\x47\x4E\x6B\xC0\x5D\x99\xB2\x96\x4F"
+		    "\xA0\x90\xC3\xA2\x23\x3B\xA1\x86\x51\x5B\xE7\xED\x1F\x61"
+		    "\x29\x70\xCE\xE2\xD7\xAF\xB8\x1B\xDD\x76\x21\x70\x48\x1C"
+		    "\xD0\x06\x91\x27\xD5\xB0\x5A\xA9\x93\xB4\xEA\x98\x8D\x8F"
+		    "\xDD\xC1\x86\xFF\xB7\xDC\x90\xA6\xC0\x8F\x4D\xF4\x35\xC9"
+		    "\x34\x02\x84\x92\x36\xC3\xFA\xB4\xD2\x7C\x70\x26\xC1\xD4"
+		    "\xDC\xB2\x60\x26\x46\xDE\xC9\x75\x1E\x76\x3D\xBA\x37\xBD"
+		    "\xF8\xFF\x94\x06\xAD\x9E\x53\x0E\xE5\xDB\x38\x2F\x41\x30"
+		    "\x01\xAE\xB0\x6A\x53\xED\x90\x27\xD8\x31\x17\x97\x27\xB0"
+		    "\x86\x5A\x89\x18\xDA\x3E\xDB\xEB\xCF\x9B\x14\xED\x44\xCE"
+		    "\x6C\xBA\xCE\xD4\xBB\x1B\xDB\x7F\x14\x47\xE6\xCC\x25\x4B"
+		    "\x33\x20\x51\x51\x2B\xD7\xAF\x42\x6F\xB8\xF4\x01\x37\x8C"
+		    "\xD2\xBF\x59\x83\xCA\x01\xC6\x4B\x92\xEC\xF0\x32\xEA\x15"
+		    "\xD1\x72\x1D\x03\xF4\x82\xD7\xCE\x6E\x74\xFE\xF6\xD5\x5E"
+		    "\x70\x2F\x46\x98\x0C\x82\xB5\xA8\x40\x31\x90\x0B\x1C\x9E"
+		    "\x59\xE7\xC9\x7F\xBE\xC7\xE8\xF3\x23\xA9\x7A\x7E\x36\xCC"
+		    "\x88\xBE\x0F\x1D\x45\xB7\xFF\x58\x5A\xC5\x4B\xD4\x07\xB2"
+		    "\x2B\x41\x54\xAA\xCC\x8F\x6D\x7E\xBF\x48\xE1\xD8\x14\xCC"
+		    "\x5E\xD2\x0F\x80\x37\xE0\xA7\x97\x15\xEE\xF2\x9B\xE3\x28"
+		    "\x06\xA1\xD5\x8B\xB7\xC5\xDA\x76\xF5\x50\xAA\x3D\x8A\x1F"
+		    "\xBF\xF0\xEB\x19\xCC\xB1\xA3\x13\xD5\x5C\xDA\x56\xC9\xEC"
+		    "\x2E\xF2\x96\x32\x38\x7F\xE8\xD7\x6E\x3C\x04\x68\x04\x3E"
+		    "\x8F\x66\x3F\x48\x60\xEE\x12\xBF\x2D\x5B\x0B\x74\x74\xD6"
+		    "\xE6\x94\xF9\x1E\x6D\xCC\x40\x24\xFF\xFF\xFF\xFF\xFF\xFF"
+		    "\xFF\xFF"
+	},
+	{
+		.id = IKEV2_DH_MODP_8192,
+		.bits = 8192,
+		.genbits = 8,
+		.generator = (uchar_t *)"\x02",
+		.prime = (uchar_t *)"\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xC9\x0F"
+		    "\xDA\xA2\x21\x68\xC2\x34\xC4\xC6\x62\x8B\x80\xDC\x1C\xD1"
+		    "\x29\x02\x4E\x08\x8A\x67\xCC\x74\x02\x0B\xBE\xA6\x3B\x13"
+		    "\x9B\x22\x51\x4A\x08\x79\x8E\x34\x04\xDD\xEF\x95\x19\xB3"
+		    "\xCD\x3A\x43\x1B\x30\x2B\x0A\x6D\xF2\x5F\x14\x37\x4F\xE1"
+		    "\x35\x6D\x6D\x51\xC2\x45\xE4\x85\xB5\x76\x62\x5E\x7E\xC6"
+		    "\xF4\x4C\x42\xE9\xA6\x37\xED\x6B\x0B\xFF\x5C\xB6\xF4\x06"
+		    "\xB7\xED\xEE\x38\x6B\xFB\x5A\x89\x9F\xA5\xAE\x9F\x24\x11"
+		    "\x7C\x4B\x1F\xE6\x49\x28\x66\x51\xEC\xE4\x5B\x3D\xC2\x00"
+		    "\x7C\xB8\xA1\x63\xBF\x05\x98\xDA\x48\x36\x1C\x55\xD3\x9A"
+		    "\x69\x16\x3F\xA8\xFD\x24\xCF\x5F\x83\x65\x5D\x23\xDC\xA3"
+		    "\xAD\x96\x1C\x62\xF3\x56\x20\x85\x52\xBB\x9E\xD5\x29\x07"
+		    "\x70\x96\x96\x6D\x67\x0C\x35\x4E\x4A\xBC\x98\x04\xF1\x74"
+		    "\x6C\x08\xCA\x18\x21\x7C\x32\x90\x5E\x46\x2E\x36\xCE\x3B"
+		    "\xE3\x9E\x77\x2C\x18\x0E\x86\x03\x9B\x27\x83\xA2\xEC\x07"
+		    "\xA2\x8F\xB5\xC5\x5D\xF0\x6F\x4C\x52\xC9\xDE\x2B\xCB\xF6"
+		    "\x95\x58\x17\x18\x39\x95\x49\x7C\xEA\x95\x6A\xE5\x15\xD2"
+		    "\x26\x18\x98\xFA\x05\x10\x15\x72\x8E\x5A\x8A\xAA\xC4\x2D"
+		    "\xAD\x33\x17\x0D\x04\x50\x7A\x33\xA8\x55\x21\xAB\xDF\x1C"
+		    "\xBA\x64\xEC\xFB\x85\x04\x58\xDB\xEF\x0A\x8A\xEA\x71\x57"
+		    "\x5D\x06\x0C\x7D\xB3\x97\x0F\x85\xA6\xE1\xE4\xC7\xAB\xF5"
+		    "\xAE\x8C\xDB\x09\x33\xD7\x1E\x8C\x94\xE0\x4A\x25\x61\x9D"
+		    "\xCE\xE3\xD2\x26\x1A\xD2\xEE\x6B\xF1\x2F\xFA\x06\xD9\x8A"
+		    "\x08\x64\xD8\x76\x02\x73\x3E\xC8\x6A\x64\x52\x1F\x2B\x18"
+		    "\x17\x7B\x20\x0C\xBB\xE1\x17\x57\x7A\x61\x5D\x6C\x77\x09"
+		    "\x88\xC0\xBA\xD9\x46\xE2\x08\xE2\x4F\xA0\x74\xE5\xAB\x31"
+		    "\x43\xDB\x5B\xFC\xE0\xFD\x10\x8E\x4B\x82\xD1\x20\xA9\x21"
+		    "\x08\x01\x1A\x72\x3C\x12\xA7\x87\xE6\xD7\x88\x71\x9A\x10"
+		    "\xBD\xBA\x5B\x26\x99\xC3\x27\x18\x6A\xF4\xE2\x3C\x1A\x94"
+		    "\x68\x34\xB6\x15\x0B\xDA\x25\x83\xE9\xCA\x2A\xD4\x4C\xE8"
+		    "\xDB\xBB\xC2\xDB\x04\xDE\x8E\xF9\x2E\x8E\xFC\x14\x1F\xBE"
+		    "\xCA\xA6\x28\x7C\x59\x47\x4E\x6B\xC0\x5D\x99\xB2\x96\x4F"
+		    "\xA0\x90\xC3\xA2\x23\x3B\xA1\x86\x51\x5B\xE7\xED\x1F\x61"
+		    "\x29\x70\xCE\xE2\xD7\xAF\xB8\x1B\xDD\x76\x21\x70\x48\x1C"
+		    "\xD0\x06\x91\x27\xD5\xB0\x5A\xA9\x93\xB4\xEA\x98\x8D\x8F"
+		    "\xDD\xC1\x86\xFF\xB7\xDC\x90\xA6\xC0\x8F\x4D\xF4\x35\xC9"
+		    "\x34\x02\x84\x92\x36\xC3\xFA\xB4\xD2\x7C\x70\x26\xC1\xD4"
+		    "\xDC\xB2\x60\x26\x46\xDE\xC9\x75\x1E\x76\x3D\xBA\x37\xBD"
+		    "\xF8\xFF\x94\x06\xAD\x9E\x53\x0E\xE5\xDB\x38\x2F\x41\x30"
+		    "\x01\xAE\xB0\x6A\x53\xED\x90\x27\xD8\x31\x17\x97\x27\xB0"
+		    "\x86\x5A\x89\x18\xDA\x3E\xDB\xEB\xCF\x9B\x14\xED\x44\xCE"
+		    "\x6C\xBA\xCE\xD4\xBB\x1B\xDB\x7F\x14\x47\xE6\xCC\x25\x4B"
+		    "\x33\x20\x51\x51\x2B\xD7\xAF\x42\x6F\xB8\xF4\x01\x37\x8C"
+		    "\xD2\xBF\x59\x83\xCA\x01\xC6\x4B\x92\xEC\xF0\x32\xEA\x15"
+		    "\xD1\x72\x1D\x03\xF4\x82\xD7\xCE\x6E\x74\xFE\xF6\xD5\x5E"
+		    "\x70\x2F\x46\x98\x0C\x82\xB5\xA8\x40\x31\x90\x0B\x1C\x9E"
+		    "\x59\xE7\xC9\x7F\xBE\xC7\xE8\xF3\x23\xA9\x7A\x7E\x36\xCC"
+		    "\x88\xBE\x0F\x1D\x45\xB7\xFF\x58\x5A\xC5\x4B\xD4\x07\xB2"
+		    "\x2B\x41\x54\xAA\xCC\x8F\x6D\x7E\xBF\x48\xE1\xD8\x14\xCC"
+		    "\x5E\xD2\x0F\x80\x37\xE0\xA7\x97\x15\xEE\xF2\x9B\xE3\x28"
+		    "\x06\xA1\xD5\x8B\xB7\xC5\xDA\x76\xF5\x50\xAA\x3D\x8A\x1F"
+		    "\xBF\xF0\xEB\x19\xCC\xB1\xA3\x13\xD5\x5C\xDA\x56\xC9\xEC"
+		    "\x2E\xF2\x96\x32\x38\x7F\xE8\xD7\x6E\x3C\x04\x68\x04\x3E"
+		    "\x8F\x66\x3F\x48\x60\xEE\x12\xBF\x2D\x5B\x0B\x74\x74\xD6"
+		    "\xE6\x94\xF9\x1E\x6D\xBE\x11\x59\x74\xA3\x92\x6F\x12\xFE"
+		    "\xE5\xE4\x38\x77\x7C\xB6\xA9\x32\xDF\x8C\xD8\xBE\xC4\xD0"
+		    "\x73\xB9\x31\xBA\x3B\xC8\x32\xB6\x8D\x9D\xD3\x00\x74\x1F"
+		    "\xA7\xBF\x8A\xFC\x47\xED\x25\x76\xF6\x93\x6B\xA4\x24\x66"
+		    "\x3A\xAB\x63\x9C\x5A\xE4\xF5\x68\x34\x23\xB4\x74\x2B\xF1"
+		    "\xC9\x78\x23\x8F\x16\xCB\xE3\x9D\x65\x2D\xE3\xFD\xB8\xBE"
+		    "\xFC\x84\x8A\xD9\x22\x22\x2E\x04\xA4\x03\x7C\x07\x13\xEB"
+		    "\x57\xA8\x1A\x23\xF0\xC7\x34\x73\xFC\x64\x6C\xEA\x30\x6B"
+		    "\x4B\xCB\xC8\x86\x2F\x83\x85\xDD\xFA\x9D\x4B\x7F\xA2\xC0"
+		    "\x87\xE8\x79\x68\x33\x03\xED\x5B\xDD\x3A\x06\x2B\x3C\xF5"
+		    "\xB3\xA2\x78\xA6\x6D\x2A\x13\xF8\x3F\x44\xF8\x2D\xDF\x31"
+		    "\x0E\xE0\x74\xAB\x6A\x36\x45\x97\xE8\x99\xA0\x25\x5D\xC1"
+		    "\x64\xF3\x1C\xC5\x08\x46\x85\x1D\xF9\xAB\x48\x19\x5D\xED"
+		    "\x7E\xA1\xB1\xD5\x10\xBD\x7E\xE7\x4D\x73\xFA\xF3\x6B\xC3"
+		    "\x1E\xCF\xA2\x68\x35\x90\x46\xF4\xEB\x87\x9F\x92\x40\x09"
+		    "\x43\x8B\x48\x1C\x6C\xD7\x88\x9A\x00\x2E\xD5\xEE\x38\x2B"
+		    "\xC9\x19\x0D\xA6\xFC\x02\x6E\x47\x95\x58\xE4\x47\x56\x77"
+		    "\xE9\xAA\x9E\x30\x50\xE2\x76\x56\x94\xDF\xC8\x1F\x56\xE8"
+		    "\x80\xB9\x6E\x71\x60\xC9\x80\xDD\x98\xED\xD3\xDF\xFF\xFF"
+		    "\xFF\xFF\xFF\xFF\xFF\xFF"
+	}
+};
+#define	NUM_DHGROUPS	(ARRAY_SIZE(dh_groups))
+
+static dhgroup_t *
+dh_get_group(int id)
+{
+	int i;
+
+	for (i = 0; i < NUM_DHGROUPS; i++) {
+		if (dh_groups[i].id == id)
+			return (&dh_groups[i]);
+	}
+
+	return (NULL);
+}
+
+CK_RV
+dh_genpair(int group, CK_OBJECT_HANDLE_PTR restrict pub,
+    CK_OBJECT_HANDLE_PTR restrict priv)
+{
+	dhgroup_t *dh;
+	CK_MECHANISM mech = { CKM_DH_PKCS_KEY_PAIR_GEN, NULL_PTR, 0 };
+	CK_ATTRIBUTE template[2];
+
+	dh = dh_get_group(group);
+	if (dh == NULL)
+		return (CKR_ATTRIBUTE_VALUE_INVALID);
+
+	template[0].type = CKA_PRIME;
+	template[0].pValue = dh->prime;
+	template[0].ulValueLen = dh->bits / 8;
+	template[1].type = CKA_BASE;
+	template[1].pValue = dh->generator;
+	template[1].ulValueLen = dh->genbits / 8;
+
+	return (C_GenerateKeyPair(p11h, &mech, template, ARRAY_SIZE(template),
+	    NULL_PTR, 0, pub, priv));
+}
+
+CK_RV
+dh_derivekey(CK_OBJECT_HANDLE privkey, uchar_t *restrict pub, size_t len,
+    CK_OBJECT_HANDLE_PTR restrict seckey)
+{
+	CK_OBJECT_CLASS key_class = CKO_SECRET_KEY;
+	CK_KEY_TYPE key_type = CKK_GENERIC_SECRET;
+	CK_ULONG key_len = 0;
+	CK_BBOOL trueval = CK_TRUE;
+	CK_MECHANISM mech = { CKM_DH_PKCS_DERIVE, pub, len };
+	CK_ATTRIBUTE template[] = {
+		{ CKA_CLASS, &key_class, sizeof (key_class) },
+		{ CKA_KEY_TYPE, &key_type, sizeof (key_type) },
+		{ CKA_VALUE_LEN, &key_len, sizeof (key_len) },
+		{ CKA_ENCRYPT, &trueval, sizeof (trueval) },
+		{ CKA_DECRYPT, &trueval, sizeof (trueval) }
+	};
+
+	return (C_DeriveKey(p11h, &mech, privkey, template,
+	    ARRAY_SIZE(template), seckey));
+}
+
+size_t
+dh_keysize(int group)
+{
+	dhgroup_t *dhg = dh_get_group(group);
+
+	if (dhg == NULL)
+		return (0);
+
+	return (dhg->bits / 8);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/dh.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,36 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.  All rights reserved.
+ */
+
+#ifndef _DH_H
+#define	_DH_H
+
+#include <sys/types.h>
+#include <security/cryptoki.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+size_t dh_keysize(int);
+CK_RV dh_genpair(int, CK_OBJECT_HANDLE_PTR restrict,
+    CK_OBJECT_HANDLE_PTR restrict);
+CK_RV dh_derivekey(CK_OBJECT_HANDLE, uchar_t *restrict, size_t,
+    CK_OBJECT_HANDLE_PTR restrict);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _DH_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/fromto.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,275 @@
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Joyent, Inc.
+ */
+
+/* Portions of the following are... */
+/*
+ * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project.
+ * All rights reserved.
+ *
+ * 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.
+ * 3. Neither the name of the project nor the names of its contributors
+ *    may be used to endorse or promote products derived from this software
+ *    without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE PROJECT 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 PROJECT 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.
+ */
+
+/*
+ * These functions provide an easy way to receive a packet with FULL address
+ * information, and send one using precise addresses.
+ */
+#ifdef lint
+/* We use X/Open style sockets */
+#define	_XOPEN_SOURCE 600
+#endif
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/int_fmtio.h>
+#include <sys/debug.h>
+#include <net/if.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <errno.h>
+#include "defs.h"
+#include "fromto.h"
+#include "ike.h"
+
+/* BEGIN CSTYLED */
+/* cstyle doesn't deal with ## __VA_ARGS__ well */
+#define	SOCKLOG(level, log, msg, sock, from, to, ...)	\
+	NETLOG(level, log, msg, from, to,		\
+	BUNYAN_T_INT32, "socket", (int32_t)(sock),	\
+	## __VA_ARGS__,					\
+	BUNYAN_T_END)
+/* END CSTYLED */
+
+/*
+ * Receive packet, with src/dst information.  It is assumed that necessary
+ * setsockopt()s (e.g. IP_SEC_OPT(NEVER)) have already performed on socket.
+ */
+ssize_t
+recvfromto(int s, uchar_t *buf, size_t buflen, int flags,
+    struct sockaddr_storage *from, socklen_t *fromlen,
+    struct sockaddr_storage *to, socklen_t *tolen)
+{
+	int otolen;
+	ssize_t len;
+	socklen_t sslen;
+	struct sockaddr_storage ss;
+	struct msghdr m;
+	struct iovec iov[2] = { 0 };
+	uint32_t cmsgbuf[64] = { 0 };
+	struct cmsghdr *cm = (struct cmsghdr *)cmsgbuf;
+	struct in6_pktinfo *pi;
+	struct sockaddr_in6 *sin6;
+	struct sockaddr_in *sin;
+
+	sslen = sizeof (ss);
+	if (getsockname(s, (struct sockaddr *)&ss, &sslen) < 0) {
+		STDERR(error, log, "getsockname() failed",
+		    BUNYAN_T_INT32, "socket", (int32_t)s);
+		return (-1);
+	}
+
+	/* Quick hack -- snapshot the current socket's port, at least. */
+	(void) memcpy(to, &ss, sslen < *tolen ? sslen : *tolen);
+
+	m.msg_name = (caddr_t)from;
+	m.msg_namelen = *fromlen;
+	iov[0].iov_base = buf;
+	iov[0].iov_len = buflen;
+	m.msg_iov = iov;
+	m.msg_iovlen = 1;
+	m.msg_control = (caddr_t)cm;
+	m.msg_controllen = sizeof (cmsgbuf);
+	if ((len = recvmsg(s, &m, flags)) < 0) {
+		SOCKLOG(error, log, "recvmsg() failed", s, from, to,
+		    BUNYAN_T_STRING, "err", strerror(errno),
+		    BUNYAN_T_INT32, "errno", (int32_t)errno);
+		return (-1);
+	}
+	if (len > buflen) {
+		/*
+		 * size_t and ssize_t should always be "long", but not in 32-
+		 * bit apps for some bizarre reason.
+		 */
+		SOCKLOG(warn, log, "Received oversized message", s, from, to,
+		    BUNYAN_T_UINT32, "msglen", (uint32_t)len,
+		    BUNYAN_T_UINT32, "buflen", (uint32_t)buflen);
+
+		errno = E2BIG;	/* Not returned from normal recvmsg()... */
+		return (-1);
+	}
+
+	if (len < sizeof (ike_header_t)) {
+		SOCKLOG(warn, log, "Received undersized message", s, from, to,
+		    BUNYAN_T_UINT32, "msglen", (uint32_t)len);
+		return (-1);
+	}
+
+	*fromlen = m.msg_namelen;
+
+	otolen = *tolen;
+	*tolen = 0;
+	for (cm = (struct cmsghdr *)CMSG_FIRSTHDR(&m);
+	    m.msg_controllen != 0 && cm;
+	    cm = (struct cmsghdr *)CMSG_NXTHDR(&m, cm)) {
+		if (ss.ss_family == AF_INET6 &&
+		    cm->cmsg_level == IPPROTO_IPV6 &&
+		    cm->cmsg_type == IPV6_PKTINFO &&
+		    otolen >= sizeof (*sin6)) {
+			/* LINTED */
+			pi = (struct in6_pktinfo *)(CMSG_DATA(cm));
+			*tolen = sizeof (*sin6);
+			sin6 = (struct sockaddr_in6 *)to;
+			(void) memset(sin6, 0, sizeof (*sin6));
+			sin6->sin6_family = AF_INET6;
+			sin6->sin6_addr = pi->ipi6_addr;
+			/* XXX other cases, such as site-local? */
+			if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+				sin6->sin6_scope_id = pi->ipi6_ifindex;
+			else
+				sin6->sin6_scope_id = 0;
+			sin6->sin6_port =
+			    ((struct sockaddr_in6 *)&ss)->sin6_port;
+			otolen = -1;	/* "to" already set */
+			continue;
+		}
+
+		if (ss.ss_family == AF_INET && cm->cmsg_level == IPPROTO_IP &&
+		    cm->cmsg_type == IP_RECVDSTADDR &&
+		    otolen >= (int)sizeof (struct sockaddr_in)) {
+			*tolen = sizeof (*sin);
+			sin = (struct sockaddr_in *)to;
+			(void) memset(sin, 0, sizeof (*sin));
+			sin->sin_family = AF_INET;
+			(void) memcpy(&sin->sin_addr, CMSG_DATA(cm),
+			    sizeof (sin->sin_addr));
+			sin->sin_port = ((struct sockaddr_in *)&ss)->sin_port;
+			otolen = -1;	/* "to" already set */
+			continue;
+		}
+	}
+
+	SOCKLOG(debug, log, "Received datagram", s, from, to,
+	    BUNYAN_T_UINT32, "msglen", (uint32_t)len);
+
+	return (len);
+}
+
+/* send packet, with fixing src/dst address pair. */
+ssize_t
+sendfromto(int s, const uchar_t *buf, size_t buflen,
+    struct sockaddr_storage *src, struct sockaddr_storage *dst)
+{
+	uint32_t cmsgbuf[64] = { 0 };
+	struct msghdr m = { 0 };
+	struct iovec iov[2];
+	struct cmsghdr *cm = (struct cmsghdr *)&cmsgbuf;
+	struct in6_pktinfo *pi6;
+	struct in_pktinfo *pi;
+	ssize_t n;
+
+	if (src->ss_family != AF_INET && src->ss_family != AF_INET6) {
+		(void) bunyan_error(log, "Unsupported address family",
+		    BUNYAN_T_STRING, "func", __func__,
+		    BUNYAN_T_STRING, "file", __FILE__,
+		    BUNYAN_T_INT32, "line", __LINE__,
+		    BUNYAN_T_UINT32, "af", (uint32_t)src->ss_family,
+		    BUNYAN_T_END);
+		errno = EAFNOSUPPORT;
+		return (-1);
+	}
+
+	if (src->ss_family != dst->ss_family) {
+		SOCKLOG(error, log, "Address family mismatch",
+		    s, src, dst,
+		    BUNYAN_T_UINT32, "srcaf", (uint32_t)src->ss_family,
+		    BUNYAN_T_UINT32, "destaf", (uint32_t)src->ss_family);
+
+		errno = EADDRNOTAVAIL;	/* XXX KEBE ASKS - Better ideas? */
+		return (-1);
+	}
+
+	SOCKLOG(debug, log, "Sending datagram", s, src, dst,
+	    BUNYAN_T_UINT32, "msglen", buflen);
+
+	m.msg_name = (caddr_t)dst;
+	iov[0].iov_base = (caddr_t)buf;
+	iov[0].iov_len = buflen;
+	m.msg_iov = iov;
+	m.msg_iovlen = 1;
+	m.msg_control = (caddr_t)cm;
+	if (src->ss_family == AF_INET6) {
+		/* v6 setup */
+		struct sockaddr_in6 *src6;
+
+		src6 = (struct sockaddr_in6 *)src;
+		m.msg_namelen = sizeof (*src6);
+		m.msg_controllen = CMSG_SPACE(sizeof (*pi6));
+		cm->cmsg_len = CMSG_LEN(sizeof (*pi6));
+		cm->cmsg_level = IPPROTO_IPV6;
+		cm->cmsg_type = IPV6_PKTINFO;
+		/* LINTED */
+		pi6 = (struct in6_pktinfo *)CMSG_DATA(cm);
+		pi6->ipi6_addr = src6->sin6_addr;
+		if (IN6_IS_ADDR_LINKLOCAL(&src6->sin6_addr) ||
+		    IN6_IS_ADDR_MULTICAST(&src6->sin6_addr)) {
+			pi6->ipi6_ifindex = src6->sin6_scope_id;
+		} else {
+			pi6->ipi6_ifindex = 0;
+		}
+	} else if (src->ss_family == AF_INET) {
+		/* v4 setup */
+		struct sockaddr_in *src4;
+
+		src4 = (struct sockaddr_in *)src;
+		m.msg_namelen = sizeof (*src4);
+		m.msg_controllen = CMSG_SPACE(sizeof (*pi));
+		cm->cmsg_len = CMSG_LEN(sizeof (*pi));
+		cm->cmsg_level = IPPROTO_IP;
+		cm->cmsg_type = IP_PKTINFO;
+		/* LINTED */
+		pi = (struct in_pktinfo *)CMSG_DATA(cm);
+		pi->ipi_addr = src4->sin_addr;
+		/* Zero out the other fields for IPv4. */
+		pi->ipi_spec_dst.s_addr = 0;
+		pi->ipi_ifindex = 0;
+	} else {
+		/*NOTREACHED*/
+		INVALID("src->ss_family");
+	}
+
+	n = sendmsg(s, &m, 0);
+	if (n < 0)
+		SOCKLOG(error, log, "sendmsg() failed", s, src, dst,
+		    BUNYAN_T_STRING, "err", strerror(errno),
+		    BUNYAN_T_UINT32, "errno", (int32_t)errno);
+
+	return (n);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/fromto.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,37 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc
+ */
+
+#ifndef _FROMTO_H
+#define	_FROMTO_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+ssize_t recvfromto(int, uchar_t *restrict, size_t, int,
+    struct sockaddr_storage *restrict, socklen_t *restrict,
+    struct sockaddr_storage *restrict, socklen_t *restrict);
+
+ssize_t sendfromto(int, const uchar_t *restrict, size_t,
+    struct sockaddr_storage *restrict, struct sockaddr_storage *restrict);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _FROMTO_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ike.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,117 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2014 Jason King.
+ */
+
+#ifndef _IKE_H
+#define	_IKE_H
+
+#include <sys/types.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/* Stuff that is the same between IKEv1 and IKEv2 */
+
+#define	IKE_GET_MAJORV(v)	(((v) & 0xf0) >> 4)
+#define	IKE_GET_MINORV(v)	((v) & 0x0f)
+#define	IKE_VERSION(_maj, _min) (((_maj) & 0xf0 << 4) | (_min) & 0x0f)
+
+#ifndef __packed
+#define	__packed __attribute__((packed))
+#endif
+
+struct ike_header {
+	uint64_t	initiator_spi;
+	uint64_t	responder_spi;
+	uint8_t		next_payload;
+	uint8_t		version;
+	uint8_t		exch_type;
+	uint8_t		flags;
+	uint32_t	msgid;
+	uint32_t	length;
+} __packed;
+typedef struct ike_header ike_header_t;
+#define	IKE_HEADER_LEN	(sizeof (ike_header_t))
+
+struct ike_payload {
+	uint8_t		pay_next;
+	uint8_t		pay_reserved;
+	uint16_t	pay_length;
+} __packed;
+typedef struct ike_payload ike_payload_t;
+
+#define	IKE_PAYLOAD_NONE	0
+
+/* Of the IKEv1 and IKEv2 payloads we recognize, this is MAX of the two */
+#define	IKE_NUM_PAYLOADS 17
+
+struct ike_prop {
+	uint8_t		prop_more;
+	uint8_t		prop_resv;
+	uint16_t	prop_len;
+	uint8_t		prop_num;
+	uint8_t		prop_proto;
+	uint8_t		prop_spilen;
+	uint8_t		prop_numxform;
+} __packed;
+typedef struct ike_prop ike_prop_t;
+#define	IKE_PROP_NONE	(0)
+#define	IKE_PROP_MORE	(2)
+
+struct ike_xform {
+	uint8_t		xf_more;
+	uint8_t		xf_resv;
+	uint16_t	xf_len;
+	uint8_t		xf_type;
+	uint8_t		xf_resv2;
+	uint16_t	xf_id;
+} __packed;
+typedef struct ike_xform ike_xform_t;
+#define	IKE_XFORM_NONE	(0)
+#define	IKE_XFORM_MORE	(3)
+
+struct ike_xf_attr {
+	uint16_t	attr_type;
+	uint16_t	attr_len;
+};
+typedef struct ike_xf_attr ike_xf_attr_t;
+#define	IKE_ATTR_TV			(1)
+#define	IKE_ATTR_TLV			(0)
+#define	IKE_ATTR_GET_TYPE(type)		((type) & 0x7fff)
+#define	IKE_ATTR_GET_FORMAT(type)	((type) & 0x8000) >> 15)
+#define	IKE_ATTR_TYPE(fmt, type) \
+	(((fmt) << 15) | ((type) & 0x7fff))
+
+struct ike_ke {
+	uint16_t	ke_group;
+	uint16_t	ke_resv;
+};
+typedef struct ike_ke ike_ke_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKE_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev1.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,130 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2014 Jason King.
+ */
+
+#ifndef _IKEV1_H
+#define	_IKEV1_H
+
+#include <sys/types.h>
+#include "ike.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	IKEV1_MAJOR_VERSION	1
+#define	IKEV1_MINOR_VERSION	0
+#define	IKEV1_VERSION		0x10
+
+typedef struct ike_header ikev1_header_t;
+
+enum ikev1_exch {
+	IKEV1_EXCH_BASE			= 1,
+	IKEV1_EXCH_IDPROT		= 2,
+	IKEV1_EXCH_AUTH_ONLY		= 3,
+	IKEV1_EXCH_AGGRESSIVE		= 4,
+	IKEV1_EXCH_INFORMATIONAL	= 5,
+};
+#define	IKEV1_VALID_EXCH(exch) \
+	(((exch) >= IKEV1_EXCH_BASE) && (exch) <= ((IKEV1_EXCH_INFORMATIONAL)))
+typedef enum ikev1_exch ikev1_exch_t;
+
+#define	IKEV1_FLAG_ENCR		(1 << 0)
+#define	IKEV1_FLAG_COMMIT	(1 << 1)
+#define	IKEV1_FLAG_AUTH_ONLY	(1 << 2)
+#define	IKEV1_FLAGS \
+	(IKEV1_FLAG_ENCR|IKEV1_FLAG_COMMIT|IKEV1_FLAG_AUTH_ONLY)
+
+typedef struct ike_payload ikev1_payload_t;
+
+enum ikev1_pay_type {
+	IKEV1_PAYLOAD_SA	= 1,
+	IKEV1_PAYLOAD_PROP	= 2,
+	IKEV1_PAYLOAD_XFORM	= 3,
+	IKEV1_PAYLOAD_KE	= 4,
+	IKEV1_PAYLOAD_ID	= 5,
+	IKEV1_PAYLOAD_CERT	= 6,
+	IKEV1_PAYLOAD_CREQ	= 7,
+	IKEV1_PAYLOAD_HASH	= 8,
+	IKEV1_PAYLOAD_SIG	= 9,
+	IKEV1_PAYLOAD_NONCE	= 10,
+	IKEV1_PAYLOAD_NOTIFY	= 11,
+	IKEV1_PAYLOAD_DELETE	= 12,
+	IKEV1_PAYLOAD_VENDOR	= 13,
+};
+typedef enum ikev1_pay_type ikev1_pay_t;
+#define	IKEV1_VALID_PAYLOAD(p) \
+	(((p) >= IKEV1_PAYLOAD_SA) && ((p) <= IKEV1_PAYLOAD_VENDOR))
+
+#define	IKEV1_SIT_IDENTITY_ONLY	(0x01)
+#define	IKEV1_SIT_SECRECY	(0x02)
+#define	IKEV1_SIT_INTEGRITY	(0x04)
+
+typedef enum ikev1_spi_proto_e {
+	IKEV1_SPI_PROTO_ISAKMP		= 1,
+	IKEv1_SPI_PROTO_IPSEC_AH	= 2,
+	IKEV1_SPI_PROTO_IPSEC_ESP	= 3,
+	IKEV1_SPI_PROTO_IPCOMP		= 4
+} ikev1_spi_proto_t;
+
+typedef struct ike_prop ikev1_prop_t;
+#define	IKEV1_PROP_LAST	0
+#define	IKEV1_PROP_MORE	2
+
+typedef struct ike_xform ikev1_xform_t;
+#define	IKEV1_XFORM_LAST	0
+#define	IKEV1_XFORM_MORE	3
+
+enum ikev1_xf_type {
+	IKEV1_XF_ENCR		= 1,
+	IKEV1_XF_HASH		= 2,
+	IKEV1_XF_AUTH		= 3,
+	IKEV1_XF_GROUP_DESC	= 4,
+	IKEV1_XF_GROUP_TYPE	= 5,
+	IKEV1_XF_GROUP_PRIME	= 6,
+	IKEV1_XF_GROUP_GEN_1	= 7,
+	IKEV1_XF_GROUP_GEN_2	= 8,
+	IKEV1_XF_GROUP_CURVE_A	= 9,
+	IKEV1_XF_GROUP_CURVE_B	= 10,
+	IKEV1_XF_LIFE_TYPE	= 11,
+	IKEV1_XF_LIFE_DUR	= 12,
+	IKEV1_XF_PRF		= 13,
+	IKEV1_XF_KEYLEN		= 14,
+	IKEV1_XF_FIELD_SIZE	= 15,
+	IKEV1_XF_GROUP_ORDER	= 16
+};
+typedef enum ikev1_xf_type ikev1_xf_type_t;
+
+typedef struct ike_xf_attr ikev1_xf_attr_t;
+#define	IKEV1_ATTR_TV			IKE_ATTR_TV
+#define	IKEV1_ATTR_TLV			IKE_ATTR_TLV
+#define	IKEV1_ATTR_GET_TYPE(t)		IKE_GET_TYPE(t)
+#define	IKEV1_ATTR_GET_FORMAT(t)	IKE_GET_FORMAT(t)
+#define	IKEV1_ATTR_TYPE(f, t)		IKE_ATTR_TYPE(f, t)
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKEV1_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev1_pkt.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,78 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Jason King.  All rights reserved.
+ */
+
+#include <stddef.h>
+#include <assert.h>
+#include <umem.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <ipsec_util.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <security/cryptoki.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <pthread.h>
+#include <sys/debug.h>
+#include <note.h>
+#include "ikev2.h"
+#include "ikev2_sa.h"
+#include "pkt.h"
+#include "pkt_impl.h"
+#include "pkcs11.h"
+
+static void
+ikev1_add_payload(pkt_t *pkt, ikev1_pay_t type)
+{
+	ASSERT(IKEV1_VALID_PAYLOAD(type));
+	ASSERT(IKE_GET_MAJORV(pkt->header.version) == IKEV1_VERSION);
+	pkt_add_payload(pkt, type, 0);
+}
+
+
+void
+ikev1_add_sa(pkt_t *pkt, uint32_t doi, uint32_t sit)
+{
+	ikev1_add_payload(pkt, IKEV1_PAYLOAD_SA);
+	buf_put32(&pkt->buf, doi);
+	buf_put32(&pkt->buf, sit);
+}
+
+void
+ikev1_add_prop(pkt_t *pkt, uint8_t propnum, ikev1_spi_proto_t spitype,
+    uint64_t spi)
+{
+	size_t spilen;
+
+	switch (spitype) {
+	case IKEV1_SPI_PROTO_ISAKMP:
+		spilen = sizeof (uint64_t);
+		break;
+	case IKEV1_SPI_PROTO_IPSEC_AH:
+	case IKEV1_SPI_PROTO_IPSEC_ESP:
+	case IKEV1_SPI_PROTO_IPCOMP:
+		spilen = sizeof (uint32_t);
+		break;
+	default:
+		INVALID(spitype);
+	}
+
+	pkt_add_prop(pkt, propnum, spitype, spi);
+}
+
+
+/* TODO */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,513 @@
+/*
+ * Copyright (c) 2010-2013 Reyk Floeter <reyk@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ *
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#ifndef _IKEV2_H
+#define	_IKEV2_H
+
+#include <inttypes.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	__packed __attribute__((packed))
+
+#define	IKEV2_VERSION		0x20	/* IKE version 2.0 */
+#define	IKEV2_KEYPAD		"Key Pad for IKEv2"	/* don't change! */
+
+/*
+ * "IKEv2 Parameters" based on the official RFC-based assignments by IANA
+ * (http://www.iana.org/assignments/ikev2-parameters/ikev2-parameters.txt)
+ */
+
+/*
+ * IKEv2 definitions of the IKE header
+ */
+
+/* IKEv2 exchange types */
+typedef enum ikev2_exch_e {
+	IKEV2_EXCH_IKE_SA_INIT		= 34,
+	IKEV2_EXCH_IKE_AUTH		= 35,
+	IKEV2_EXCH_CREATE_CHILD_SA	= 36,
+	IKEV2_EXCH_INFORMATIONAL	= 37,
+	IKEV2_EXCH_IKE_SESSION_RESUME	= 38
+} ikev2_exch_t;
+
+/* IKEv2 message flags */
+#define	IKEV2_FLAG_INITIATOR	0x08	/* Sent by the initiator */
+#define	IKEV2_FLAG_VERSION	0x10	/* Supports a higher IKE version */
+#define	IKEV2_FLAG_RESPONSE	0x20	/* Message is a response */
+
+/*
+ * IKEv2 payloads
+ */
+struct ikev2_payload {
+	uint8_t		pld_nextpayload;	/* Next payload type */
+	uint8_t		pld_reserved;		/* Contains the critical bit */
+	uint16_t	pld_length;		/* Payload length with header */
+} __packed;
+
+#define	IKEV2_CRITICAL_PAYLOAD	0x01	/* First bit in the reserved field */
+
+/* IKEv2 payload types */
+typedef enum ikev2_pay_type {
+	IKEV2_PAYLOAD_NONE =	0,	/* No payload */
+	IKEV2_PAYLOAD_SA =	33,	/* Security Association */
+	IKEV2_PAYLOAD_KE =	34,	/* Key Exchange */
+	IKEV2_PAYLOAD_IDi =	35,	/* Identification - Initiator */
+	IKEV2_PAYLOAD_IDr =	36,	/* Identification - Responder */
+	IKEV2_PAYLOAD_CERT =	37,	/* Certificate */
+	IKEV2_PAYLOAD_CERTREQ =	38,	/* Certificate Request */
+	IKEV2_PAYLOAD_AUTH =	39,	/* Authentication */
+	IKEV2_PAYLOAD_NONCE =	40,	/* Nonce */
+	IKEV2_PAYLOAD_NOTIFY =	41,	/* Notify */
+	IKEV2_PAYLOAD_DELETE =	42,	/* Delete */
+	IKEV2_PAYLOAD_VENDOR =	43,	/* Vendor ID */
+	IKEV2_PAYLOAD_TSi =	44,	/* Traffic Selector - Initiator */
+	IKEV2_PAYLOAD_TSr =	45,	/* Traffic Selector - Responder */
+	IKEV2_PAYLOAD_SK =	46,	/* Encrypted */
+	IKEV2_PAYLOAD_CP =	47,	/* Configuration Payload */
+	IKEV2_PAYLOAD_EAP =	48,	/* Extensible Authentication */
+	IKEV2_PAYLOAD_GSPM =	49	/* RFC6467 Generic Secure Password */
+} ikev2_pay_type_t;
+
+#define	IKEV2_PAYLOAD_MIN	IKEV2_PAYLOAD_SA
+#define	IKEV2_PAYLOAD_MAX	IKEV2_PAYLOAD_GSPM
+#define	IKEV2_NUM_PAYLOADS	(IKEV2_PAYLOAD_MAX - IKEV2_PAYLOAD_MIN + 1)
+#define	IKEV2_VALID_PAYLOAD(paytype) \
+	(((paytype) >= IKEV2_PAYLOAD_MIN) && ((paytype) <= IKEV2_PAYLOAD_MAX))
+
+/*
+ * SA payload
+ */
+
+struct ikev2_sa_proposal {
+	uint8_t		proto_more;		/* Last proposal or more */
+	uint8_t		proto_reserved;		/* Must be set to zero */
+	uint16_t	proto_length;		/* Proposal length */
+	uint8_t		proto_proposalnr;	/* Proposal number */
+	uint8_t		proto_protoid;		/* Protocol Id */
+	uint8_t		proto_spisize;		/* SPI size */
+	uint8_t		proto_transforms;	/* Number of transforms */
+	/* Followed by variable-length SPI */
+	/* Followed by variable-length transforms */
+} __packed;
+
+#define	IKEV2_PROP_LAST	0
+#define	IKEV2_PROP_MORE	2
+
+typedef enum ikev2_spi_proto_e {
+	IKEV2_PROTO_NONE		= 0,	/* None */
+	IKEV2_PROTO_IKE			= 1,	/* IKEv2 */
+	IKEV2_PROTO_AH			= 2,	/* AH */
+	IKEV2_PROTO_ESP			= 3,	/* ESP */
+	IKEV2_PROTO_FC_ESP_HEADER	= 4,	/* RFC4595 */
+	IKEV2_PROTO_FC_CT_AUTH		= 5	/* RFC4595 */
+} ikev2_spi_proto_t;
+
+struct ikev2_transform {
+	uint8_t		xf_more;		/* Last transform or more */
+	uint8_t		xf_reserved;		/* Must be set to zero */
+	uint16_t	xf_length;		/* Transform length */
+	uint8_t		xf_type;		/* Transform type */
+	uint8_t		xf_reserved1;		/* Must be set to zero */
+	uint16_t	xf_id;		/* Transform Id */
+	/* Followed by variable-length transform attributes */
+} __packed;
+
+#define	IKEV2_XF_LAST		0
+#define	IKEV2_XF_MORE		3
+
+typedef enum ikev2_xf_type_e {
+	IKEV2_XF_ENCR	= 1,	/* Encryption */
+	IKEV2_XF_PRF	= 2,	/* Pseudo-Random Function */
+	IKEV2_XF_AUTH	= 3,	/* Integrity Algorithm */
+	IKEV2_XF_DH	= 4,	/* Diffie-Hellman Group */
+	IKEV2_XF_ESN	= 5	/* Extended Sequence Numbers */
+} ikev2_xf_type_t;
+#define	IKEV2_XF_MAX		6
+
+typedef enum ikev2_encr_e {
+	IKEV2_ENCR_NONE			= 0,	/* None */
+	IKEV2_ENCR_DES_IV64		= 1,	/* RFC1827 */
+	IKEV2_ENCR_DES			= 2,	/* RFC2405 */
+	IKEV2_ENCR_3DES			= 3,	/* RFC2451 */
+	IKEV2_ENCR_RC5			= 4,	/* RFC2451 */
+	IKEV2_ENCR_IDEA			= 5,	/* RFC2451 */
+	IKEV2_ENCR_CAST			= 6,	/* RFC2451 */
+	IKEV2_ENCR_BLOWFISH		= 7,	/* RFC2451 */
+	IKEV2_ENCR_3IDEA		= 8,	/* RFC2451 */
+	IKEV2_ENCR_DES_IV32		= 9,	/* DESIV32 */
+	IKEV2_ENCR_RC4			= 10,	/* RFC2451 */
+	IKEV2_ENCR_NULL			= 11,	/* RFC2410 */
+	IKEV2_ENCR_AES_CBC		= 12,	/* RFC3602 */
+	IKEV2_ENCR_AES_CTR		= 13,	/* RFC3664 */
+	IKEV2_ENCR_AES_CCM_8		= 14,	/* RFC5282 */
+	IKEV2_ENCR_AES_CCM_12		= 15,	/* RFC5282 */
+	IKEV2_ENCR_AES_CCM_16		= 16,	/* RFC5282 */
+	IKEV2_ENCR_AES_GCM_8		= 18,	/* RFC5282 */
+	IKEV2_ENCR_AES_GCM_12		= 19,	/* RFC5282 */
+	IKEV2_ENCR_AES_GCM_16		= 20,	/* RFC5282 */
+	IKEV2_ENCR_NULL_AES_GMAC	= 21,	/* RFC4543 */
+	IKEV2_ENCR_XTS_AES		= 22,	/* IEEE P1619 */
+	IKEV2_ENCR_CAMELLIA_CBC		= 23,	/* RFC5529 */
+	IKEV2_ENCR_CAMELLIA_CTR		= 24,	/* RFC5529 */
+	IKEV2_ENCR_CAMELLIA_CCM_8	= 25,	/* RFC5529 */
+	IKEV2_ENCR_CAMELLIA_CCM_12	= 26,	/* RFC5529 */
+	IKEV2_ENCR_CAMELLIA_CCM_16	= 27,	/* RFC5529 */
+} ikev2_xf_encr_t;
+
+#define	IKEV2_IPCOMP_OUI		1	/* RFC5996 */
+#define	IKEV2_IPCOMP_DEFLATE		2	/* RFC2394 */
+#define	IKEV2_IPCOMP_LZS		3	/* RFC2395 */
+#define	IKEV2_IPCOMP_LZJH		4	/* RFC3051 */
+
+typedef enum ikev2_prf {
+	IKEV2_PRF_HMAC_MD5		= 1,	/* RFC2104 */
+	IKEV2_PRF_HMAC_SHA1		= 2,	/* RFC2104 */
+	IKEV2_PRF_HMAC_TIGER		= 3,	/* RFC2104 */
+	IKEV2_PRF_AES128_XCBC		= 4,	/* RFC3664 */
+	IKEV2_PRF_HMAC_SHA2_256		= 5,	/* RFC4868 */
+	IKEV2_PRF_HMAC_SHA2_384		= 6,	/* RFC4868 */
+	IKEV2_PRF_HMAC_SHA2_512		= 7,	/* RFC4868 */
+	IKEV2_PRF_AES128_CMAC		= 8	/* RFC4615 */
+} ikev2_prf_t;
+
+typedef enum ikev2_xf_auth_e {
+	IKEV2_XF_AUTH_NONE			= 0,	/* No Authentication */
+	IKEV2_XF_AUTH_HMAC_MD5_96		= 1,	/* RFC2403 */
+	IKEV2_XF_AUTH_HMAC_SHA1_96		= 2,	/* RFC2404 */
+	IKEV2_XF_AUTH_DES_MAC			= 3,	/* DES-MAC */
+	IKEV2_XF_AUTH_KPDK_MD5			= 4,	/* RFC1826 */
+	IKEV2_XF_AUTH_AES_XCBC_96		= 5,	/* RFC3566 */
+	IKEV2_XF_AUTH_HMAC_MD5_128		= 6,	/* RFC4595 */
+	IKEV2_XF_AUTH_HMAC_SHA1_160		= 7,	/* RFC4595 */
+	IKEV2_XF_AUTH_AES_CMAC_96		= 8,	/* RFC4494 */
+	IKEV2_XF_AUTH_AES_128_GMAC		= 9,	/* RFC4543 */
+	IKEV2_XF_AUTH_AES_192_GMAC		= 10,	/* RFC4543 */
+	IKEV2_XF_AUTH_AES_256_GMAC		= 11,	/* RFC4543 */
+	IKEV2_XF_AUTH_HMAC_SHA2_256_128 	= 12,	/* RFC4868 */
+	IKEV2_XF_AUTH_HMAC_SHA2_384_192 	= 13,	/* RFC4868 */
+	IKEV2_XF_AUTH_HMAC_SHA2_512_256 	= 14	/* RFC4868 */
+} ikev2_xf_auth_t;
+
+typedef enum ikev2_dh {
+	IKEV2_DH_NONE			= 0,	/* No DH */
+	IKEV2_DH_MODP_768		= 1,	/* DH Group 1 */
+	IKEV2_DH_MODP_1024		= 2,	/* DH Group 2 */
+	IKEV2_DH_EC2N_155		= 3,	/* DH Group 3 */
+	IKEV2_DH_EC2N_185		= 4,	/* DH Group 3 */
+	IKEV2_DH_MODP_1536		= 5,	/* DH Group 5 */
+	IKEV2_DH_MODP_2048		= 14,	/* DH Group 14 */
+	IKEV2_DH_MODP_3072		= 15,	/* DH Group 15 */
+	IKEV2_DH_MODP_4096		= 16,	/* DH Group 16 */
+	IKEV2_DH_MODP_6144		= 17,	/* DH Group 17 */
+	IKEV2_DH_MODP_8192		= 18,	/* DH Group 18 */
+	IKEV2_DH_ECP_256		= 19,	/* DH Group 19 */
+	IKEV2_DH_ECP_384		= 20,	/* DH Group 20 */
+	IKEV2_DH_ECP_521		= 21,	/* DH Group 21 */
+	IKEV2_DH_MODP_1024_160		= 22,	/* DH Group 22 */
+	IKEV2_DH_MODP_2048_224		= 23,	/* DH Group 23 */
+	IKEV2_DH_MODP_2048_256		= 24,	/* DH Group 24 */
+	IKEV2_DH_ECP_192		= 25,	/* DH Group 25 */
+	IKEV2_DH_ECP_224		= 26,	/* DH Group 26 */
+	IKEV2_DH_BRAINPOOL_P224R1	= 27,	/* DH Group 27 */
+	IKEV2_DH_BRAINPOOL_P256R1	= 28,	/* DH Group 28 */
+	IKEV2_DH_BRAINPOOL_P384R1	= 29,	/* DH Group 29 */
+	IKEV2_DH_BRAINPOOL_P512R1	= 30	/* DH Group 30 */
+} ikev2_dh_t;
+#define	IKEV2_DH_MAX			31
+
+#define	IKEV2_XFORMESN_NONE		0	/* No ESN */
+#define	IKEV2_XFORMESN_ESN		1	/* ESN */
+
+struct ikev2_attribute {
+	uint16_t	attr_type;	/* Attribute type */
+	uint16_t	attr_length;	/* Attribute length or value */
+	/* Followed by variable length (TLV) */
+} __packed;
+
+#define	IKEV2_ATTRAF_TLV		0x0000	/* Type-Length-Value format */
+#define	IKEV2_ATTRAF_TV			0x8000	/* Type-Value format */
+
+typedef enum ikev2_xf_attr_type {
+	IKEV2_XF_ATTR_KEYLEN	= 14		/* Key length */
+} ikev2_xf_attr_type_t;
+
+/*
+ * KE Payload
+ */
+struct ikev2_ke {
+	uint16_t	 kex_dhgroup;		/* DH Group # */
+	uint16_t	 kex_reserved;		/* Reserved */
+} __packed;
+
+/*
+ * N payload
+ */
+struct ikev2_notify {
+	uint8_t		n_protoid;		/* Protocol Id */
+	uint8_t		n_spisize;		/* SPI size */
+	uint16_t	n_type;		/* Notify message type */
+	/* Followed by variable length SPI */
+	/* Followed by variable length notification data */
+} __packed;
+
+/*
+ * NOTIFY types.  We don't support all of these, however for observability
+ * and debugging purposes, we try to maintain a list of all known values.
+ */
+typedef enum ikev2_notify_type {
+	IKEV2_N_UNSUPPORTED_CRITICAL_PAYLOAD	= 1,		/* RFC4306 */
+	IKEV2_N_INVALID_IKE_SPI			= 4,		/* RFC4306 */
+	IKEV2_N_INVALID_MAJOR_VERSION		= 5,		/* RFC4306 */
+	IKEV2_N_INVALID_SYNTAX			= 7,		/* RFC4306 */
+	IKEV2_N_INVALID_MESSAGE_ID		= 9,		/* RFC4306 */
+	IKEV2_N_INVALID_SPI			= 11,		/* RFC4306 */
+	IKEV2_N_NO_PROPOSAL_CHOSEN		= 14,		/* RFC4306 */
+	IKEV2_N_INVALID_KE_PAYLOAD		= 17,		/* RFC4306 */
+	IKEV2_N_AUTHENTICATION_FAILED		= 24,		/* RFC4306 */
+	IKEV2_N_SINGLE_PAIR_REQUIRED		= 34,		/* RFC4306 */
+	IKEV2_N_NO_ADDITIONAL_SAS		= 35,		/* RFC4306 */
+	IKEV2_N_INTERNAL_ADDRESS_FAILURE	= 36,		/* RFC4306 */
+	IKEV2_N_FAILED_CP_REQUIRED		= 37,		/* RFC4306 */
+	IKEV2_N_TS_UNACCEPTABLE			= 38,		/* RFC4306 */
+	IKEV2_N_INVALID_SELECTORS		= 39,		/* RFC4306 */
+	IKEV2_N_UNACCEPTABLE_ADDRESSES		= 40,		/* RFC4555 */
+	IKEV2_N_UNEXPECTED_NAT_DETECTED		= 41,		/* RFC4555 */
+	IKEV2_N_USE_ASSIGNED_HoA		= 42,		/* RFC5026 */
+	IKEV2_N_TEMPORARY_FAILURE		= 43,		/* RFC5996 */
+	IKEV2_N_CHILD_SA_NOT_FOUND		= 44,		/* RFC5996 */
+	IKEV2_N_INITIAL_CONTACT			= 16384,	/* RFC4306 */
+	IKEV2_N_SET_WINDOW_SIZE			= 16385,	/* RFC4306 */
+	IKEV2_N_ADDITIONAL_TS_POSSIBLE		= 16386,	/* RFC4306 */
+	IKEV2_N_IPCOMP_SUPPORTED		= 16387,	/* RFC4306 */
+	IKEV2_N_NAT_DETECTION_SOURCE_IP		= 16388,	/* RFC4306 */
+	IKEV2_N_NAT_DETECTION_DESTINATION_IP	= 16389,	/* RFC4306 */
+	IKEV2_N_COOKIE				= 16390,	/* RFC4306 */
+	IKEV2_N_USE_TRANSPORT_MODE		= 16391,	/* RFC4306 */
+	IKEV2_N_HTTP_CERT_LOOKUP_SUPPORTED	= 16392,	/* RFC4306 */
+	IKEV2_N_REKEY_SA			= 16393,	/* RFC4306 */
+	IKEV2_N_ESP_TFC_PADDING_NOT_SUPPORTED	= 16394,	/* RFC4306 */
+	IKEV2_N_NON_FIRST_FRAGMENTS_ALSO	= 16395,	/* RFC4306 */
+	IKEV2_N_MOBIKE_SUPPORTED		= 16396,	/* RFC4555 */
+	IKEV2_N_ADDITIONAL_IP4_ADDRESS		= 16397,	/* RFC4555 */
+	IKEV2_N_ADDITIONAL_IP6_ADDRESS		= 16398,	/* RFC4555 */
+	IKEV2_N_NO_ADDITIONAL_ADDRESSES		= 16399,	/* RFC4555 */
+	IKEV2_N_UPDATE_SA_ADDRESSES		= 16400,	/* RFC4555 */
+	IKEV2_N_COOKIE2				= 16401,	/* RFC4555 */
+	IKEV2_N_NO_NATS_ALLOWED			= 16402,	/* RFC4555 */
+	IKEV2_N_AUTH_LIFETIME			= 16403,	/* RFC4478 */
+	IKEV2_N_MULTIPLE_AUTH_SUPPORTED		= 16404,	/* RFC4739 */
+	IKEV2_N_ANOTHER_AUTH_FOLLOWS		= 16405,	/* RFC4739 */
+	IKEV2_N_REDIRECT_SUPPORTED		= 16406,	/* RFC5685 */
+	IKEV2_N_REDIRECT			= 16407,	/* RFC5685 */
+	IKEV2_N_REDIRECTED_FROM			= 16408,	/* RFC5685 */
+	IKEV2_N_TICKET_LT_OPAQUE		= 16409,	/* RFC5723 */
+	IKEV2_N_TICKET_REQUEST			= 16410,	/* RFC5723 */
+	IKEV2_N_TICKET_ACK			= 16411,	/* RFC5723 */
+	IKEV2_N_TICKET_NACK			= 16412,	/* RFC5723 */
+	IKEV2_N_TICKET_OPAQUE			= 16413,	/* RFC5723 */
+	IKEV2_N_LINK_ID				= 16414,	/* RFC5739 */
+	IKEV2_N_USE_WESP_MODE			= 16415,
+			/* RFC-ietf-ipsecme-traffic-visibility-12.txt */
+	IKEV2_N_ROHC_SUPPORTED			= 16416,
+			/* RFC-ietf-rohc-ikev2-extensions-hcoipsec-12.txt */
+	IKEV2_N_EAP_ONLY_AUTHENTICATION		= 16417,	/* RFC5998 */
+	IKEV2_N_CHILDLESS_IKEV2_SUPPORTED	= 16418,	/* RFC6023 */
+	IKEV2_N_QUICK_CRASH_DETECTION		= 16419,	/* RFC6290 */
+	IKEV2_N_IKEV2_MESSAGE_ID_SYNC_SUPPORTED	= 16420,	/* RFC6311 */
+	IKEV2_N_IPSEC_REPLAY_CTR_SYNC_SUPPORTED	= 16421,	/* RFC6311 */
+	IKEV2_N_IKEV2_MESSAGE_ID_SYNC		= 16422,	/* RFC6311 */
+	IKEV2_N_IPSEC_REPLAY_CTR_SYNC		= 16423,	/* RFC6311 */
+	IKEV2_N_SECURE_PASSWORD_METHODS		= 16424,	/* RFC6467 */
+	IKEV2_N_PSK_PERSIST			= 16425,	/* RFC6631 */
+	IKEV2_N_PSK_CONFIRM			= 16426,	/* RFC6631 */
+	IKEV2_N_ERX_SUPPORTED			= 16427,	/* RFC6867 */
+	IKEV2_N_IFOM_CAPABILITY			= 16428		/* OA3GPP */
+} ikev2_notify_type_t;
+
+/*
+ * DELETE payload
+ */
+struct ikev2_delete {
+	uint8_t 	del_protoid;		/* Protocol Id */
+	uint8_t		del_spisize;		/* SPI size */
+	uint16_t	del_nspi;		/* Number of SPIs */
+	/* Followed by variable length SPIs */
+} __packed;
+
+/*
+ * ID payload
+ */
+struct ikev2_id {
+	uint8_t	 id_type;		/* Id type */
+	uint8_t	 id_reserved[3];	/* Reserved */
+	/* Followed by the identification data */
+} __packed;
+
+typedef enum ikev2_id_type {
+	IKEV2_ID_IPV4_ADDR	= 1,	/* RFC7296 */
+	IKEV2_ID_FQDN		= 2,	/* RFC7296 */
+	IKEV2_ID_RFC822_ADDR	= 3,	/* RFC7296 */
+	IKEV2_ID_IPV6_ADDR	= 5,	/* RFC7296 */
+	IKEV2_ID_DER_ASN1_DN	= 9,	/* RFC7296 */
+	IKEV2_ID_DER_ASN1_GN	= 10,	/* RFC7296 */
+	IKEV2_ID_KEY_ID		= 11,	/* RFC7296 */
+	IKEV2_ID_FC_NAME	= 12	/* RFC4595 */
+} ikev2_id_type_t;
+
+/*
+ * CERT/CERTREQ payloads
+ */
+typedef enum ikev2_cert {
+	IKEV2_CERT_NONE =			0,	/* None */
+	IKEV2_CERT_X509_PKCS7 =			1,	/* RFC4306 */
+	IKEV2_CERT_PGP =			2,	/* RFC4306 */
+	IKEV2_CERT_DNS_SIGNED_KEY =		3,	/* RFC4306 */
+	IKEV2_CERT_X509_CERT =			4,	/* RFC4306 */
+	IKEV2_CERT_KERBEROS_TOKEN =		6,	/* RFC4306 */
+	IKEV2_CERT_CRL =			7,	/* RFC4306 */
+	IKEV2_CERT_ARL =			8,	/* RFC4306 */
+	IKEV2_CERT_SPKI =			9,	/* RFC4306 */
+	IKEV2_CERT_X509_ATTR =			10,	/* RFC4306 */
+	IKEV2_CERT_RSA_KEY =			11,	/* RFC4306 */
+	IKEV2_CERT_HASHURL_X509 =		12,	/* RFC4306 */
+	IKEV2_CERT_HASHURL_X509_BUNDLE =	13,	/* RFC4306 */
+	IKEV2_CERT_OCSP =			14	/* RFC4806 */
+} ikev2_cert_t;
+
+/*
+ * TSi/TSr payloads
+ */
+struct ikev2_tsp {
+	uint8_t	tsp_count;		/* Number of TSs */
+	uint8_t	tsp_reserved[3];	/* Reserved */
+	/* Followed by the traffic selectors */
+} __packed;
+
+struct ikev2_ts {
+	uint8_t		ts_type;		/* TS type */
+	uint8_t		ts_protoid;		/* Protocol Id */
+	uint16_t	ts_length;		/* Length */
+	uint16_t	ts_startport;		/* Start port */
+	uint16_t	ts_endport;		/* End port */
+} __packed;
+
+typedef enum ikev2_ts_type {
+	IKEV2_TS_IPV4_ADDR_RANGE =	7,	/* RFC4306 */
+	IKEV2_TS_IPV6_ADDR_RANGE =	8,	/* RFC4306 */
+	IKEV2_TS_FC_ADDR_RANGE =	9	/* RFC4595 */
+} ikev2_ts_type_t;
+
+/*
+ * AUTH payload
+ */
+struct ikev2_auth {
+	uint8_t	auth_method;		/* Signature type */
+	uint8_t	auth_reserved[3];	/* Reserved */
+	/* Followed by the signature */
+} __packed;
+
+typedef enum ikev2_auth_type {
+	IKEV2_AUTH_NONE =		0,	/* None */
+	IKEV2_AUTH_RSA_SIG =		1,	/* RFC4306 */
+	IKEV2_AUTH_SHARED_KEY_MIC =	2,	/* RFC4306 */
+	IKEV2_AUTH_DSS_SIG =		3,	/* RFC4306 */
+	IKEV2_AUTH_ECDSA_256 =		9,	/* RFC4754 */
+	IKEV2_AUTH_ECDSA_384 =		10,	/* RFC4754 */
+	IKEV2_AUTH_ECDSA_512 =		11,	/* RFC4754 */
+	IKEV2_AUTH_GSPM =		12	/* RFC6467 */
+} ikev2_auth_type_t;
+
+/*
+ * CP payload
+ */
+struct ikev2_cp {
+	uint8_t	cp_type;
+	uint8_t	cp_reserved[3];
+	/* Followed by the attributes */
+} __packed;
+
+typedef enum ikev2_cfg_type {
+	IKEV2_CP_REQUEST	= 1,	/* CFG-Request */
+	IKEV2_CP_REPLY		= 2,	/* CFG-Reply */
+	IKEV2_CP_SET		= 3,	/* CFG-SET */
+	IKEV2_CP_ACK		= 4	/* CFG-ACK */
+} ikev2_cfg_type_t;
+
+struct ikev2_cfg {
+	uint16_t	cfg_type;	/* first bit must be set to zero */
+	uint16_t	cfg_length;
+	/* Followed by variable-length data */
+} __packed;
+
+typedef enum ikev2_cfg_attr_type {
+	IKEV2_CFG_INTERNAL_IP4_ADDRESS		= 1,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP4_NETMASK		= 2,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP4_DNS		= 3,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP4_NBNS		= 4,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_ADDRESS_EXPIRY	= 5,	/* RFC4306 */
+	IKEV2_CFG_INTERNAL_IP4_DHCP		= 6,	/* RFC5996 */
+	IKEV2_CFG_APPLICATION_VERSION		= 7,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP6_ADDRESS		= 8,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP6_DNS		= 10,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP6_NBNS		= 11,	/* RFC4306 */
+	IKEV2_CFG_INTERNAL_IP6_DHCP		= 12,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP4_SUBNET		= 13,	/* RFC5996 */
+	IKEV2_CFG_SUPPORTED_ATTRIBUTES		= 14,	/* RFC5996 */
+	IKEV2_CFG_INTERNAL_IP6_SUBNET		= 15,	/* RFC5996 */
+	IKEV2_CFG_MIP6_HOME_PREFIX		= 16,	/* RFC5026 */
+	IKEV2_CFG_INTERNAL_IP6_LINK		= 17,	/* RFC5739 */
+	IKEV2_CFG_INTERNAL_IP6_PREFIX		= 18,	/* RFC5739 */
+	IKEV2_CFG_HOME_AGENT_ADDRESS		= 19,
+/* BEGIN CSTYLED */
+		/* http://www.3gpp.org/ftp/Specs/html-info/24302.htm */
+/* END CSTYLED */
+	IKEV2_CFG_INTERNAL_IP4_SERVER		= 23456, /* MS-IKEE */
+	IKEV2_CFG_INTERNAL_IP6_SERVER		= 23457  /* MS-IKEE */
+} ikev2_cfg_attr_type_t;
+
+/* MD5 sum of "ILLUMOS_1 2017/08/21 29.718 -95.390" */
+#define	VENDOR_STR_ILLUMOS_1	"6a3b8d3af106854d3a2c56c50df729cf"
+
+/* The vendor types + versions we recognize */
+typedef enum vendor {
+	VENDOR_UNKNOWN			= 0,
+	VENDOR_ILLUMOS_1		= 1
+} vendor_t;
+
+typedef struct ikev2_payload ikev2_payload_t;
+typedef struct ikev2_sa_proposal ikev2_sa_proposal_t;
+typedef struct ikev2_transform ikev2_transform_t;
+typedef struct ikev2_attribute ikev2_attribute_t;
+typedef struct ikev2_ke ikev2_ke_t;
+typedef struct ikev2_notify ikev2_notify_t;
+typedef struct ikev2_delete ikev2_delete_t;
+typedef struct ikev2_id ikev2_id_t;
+typedef struct ikev2_tsp ikev2_tsp_t;
+typedef struct ikev2_ts ikev2_ts_t;
+typedef struct ikev2_auth ikev2_auth_t;
+typedef struct ikev2_cp ikev2_cp_t;
+typedef struct ikev2_cfg ikev2_cfg_t;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKEV2_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_common.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,134 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+#include <sys/types.h>
+#include <net/pfkeyv2.h>
+#include <sys/debug.h>
+#include "defs.h"
+#include "ikev2.h"
+#include "ikev2_pkt.h"
+
+ikev2_xf_auth_t ikev2_pfkey_to_auth(int);
+ikev2_xf_encr_t ikev2_pfkey_to_encr(int);
+
+boolean_t
+ikev2_sa_from_acquire(pkt_t *pkt, parsedmsg_t *pmsg, uint32_t spi,
+    ikev2_dh_t dh)
+{
+	sadb_msg_t *samsg = pmsg->pmsg_samsg;
+	sadb_sa_t *sa;
+	sadb_prop_t *prop;
+	sadb_comb_t *comb;
+	boolean_t ok;
+	ikev2_spi_proto_t spi_type = IKEV2_PROTO_NONE;
+
+	ASSERT3U(samsg->sadb_msg_type, ==, SADB_ACQUIRE);
+
+	switch (samsg->sadb_msg_satype) {
+	case SADB_SATYPE_AH:
+		spi_type = IKEV2_PROTO_AH;
+		break;
+	case SADB_SATYPE_ESP:
+		spi_type = IKEV2_PROTO_ESP;
+		break;
+	default:
+		INVALID("sadb_msg_satype");
+	}
+
+	prop = (sadb_prop_t *)pmsg->pmsg_exts[SADB_EXT_PROPOSAL];
+	ASSERT3U(prop->sadb_prop_exttype, ==, SADB_EXT_PROPOSAL);
+
+	ok = ikev2_add_sa(pkt);
+
+	comb = (sadb_comb_t *)(prop + 1);
+	for (size_t i = 0; i < prop->sadb_x_prop_numecombs; i++, comb++) {
+		ok &= ikev2_add_prop(pkt, i + 1, spi_type, spi);
+
+		if (comb->sadb_comb_encrypt != SADB_EALG_NONE) {
+			ikev2_xf_encr_t encr;
+			uint16_t minbits, maxbits;
+
+			encr = ikev2_pfkey_to_encr(comb->sadb_comb_encrypt);
+			minbits = comb->sadb_comb_encrypt_minbits;
+			maxbits = comb->sadb_comb_encrypt_maxbits;
+			ok &= ikev2_add_xf_encr(pkt, encr, minbits, maxbits);
+		}
+
+		if (comb->sadb_comb_auth != SADB_AALG_NONE) {
+			ikev2_xf_auth_t xf_auth;
+			/*
+			 * nothing currently supports this either local algs
+			 * or the IKE protocol
+			 */
+			VERIFY3U(comb->sadb_comb_auth_minbits, ==, 0);
+			VERIFY3U(comb->sadb_comb_auth_maxbits, ==, 0);
+
+			xf_auth = ikev2_pfkey_to_auth(comb->sadb_comb_auth);
+			ok &= ikev2_add_xform(pkt, IKEV2_XF_AUTH, xf_auth);
+		}
+
+		if (dh != IKEV2_DH_NONE)
+			ok &= ikev2_add_xform(pkt, IKEV2_XF_DH, dh);
+	}
+
+	return (ok);
+}
+
+ikev2_xf_auth_t
+ikev2_pfkey_to_auth(int alg)
+{
+	switch (alg) {
+	case SADB_AALG_NONE:
+	case SADB_AALG_SHA256HMAC:
+	case SADB_AALG_SHA384HMAC:
+	case SADB_AALG_SHA512HMAC:
+		/* these values all correspond */
+		return (alg);
+	case SADB_AALG_MD5HMAC:
+		/* this one does not */
+		return (IKEV2_XF_AUTH_HMAC_MD5_96);
+	case SADB_AALG_SHA1HMAC:
+		/* nor does this one */
+		return (IKEV2_XF_AUTH_HMAC_SHA1_96);
+	default:
+		INVALID("alg");
+		/*NOTREACHED*/
+		return (alg);
+	}
+}
+
+ikev2_xf_encr_t
+ikev2_pfkey_to_encr(int alg)
+{
+	switch (alg) {
+	case SADB_EALG_NONE:
+	case SADB_EALG_DESCBC:
+	case SADB_EALG_3DESCBC:
+	case SADB_EALG_BLOWFISH:
+	case SADB_EALG_NULL:
+	case SADB_EALG_AES:	/* CBC */
+	case SADB_EALG_AES_CCM_8:
+	case SADB_EALG_AES_CCM_12:
+	case SADB_EALG_AES_CCM_16:
+	case SADB_EALG_AES_GCM_8:
+	case SADB_EALG_AES_GCM_12:
+	case SADB_EALG_AES_GCM_16:
+		return (alg);
+	default:
+		INVALID("alg");
+		/*NOTREACHED*/
+		return (alg);
+	}
+}
+
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_enum.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,394 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017 Joyent, Inc.
+ */
+
+#include "ikev2_enum.h"
+#include "ikev2.h"
+
+#define	STR(x) case x: return (#x)
+
+/*
+ * NOTE: We intentionally use switch statements instead of arrays for
+ * mapping enumerated constants to strings.  Doing so will allow the
+ * compiler to flag missing conversions for any entries that get added
+ * to enumerated types as long as no default clause is used in the switch.
+ */
+
+const char *
+ikev2_exch_str(ikev2_exch_t id)
+{
+	switch (id) {
+	case IKEV2_EXCH_IKE_SA_INIT:
+		return ("SA_INIT");
+	case IKEV2_EXCH_IKE_AUTH:
+		return ("AUTH");
+	case IKEV2_EXCH_CREATE_CHILD_SA:
+		return ("CREATE_CHILD_SA");
+	case IKEV2_EXCH_INFORMATIONAL:
+		return ("INFORMATIONAL");
+	case IKEV2_EXCH_IKE_SESSION_RESUME:
+		return ("SESSION_RESUME");
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_pay_str(ikev2_pay_type_t id)
+{
+	switch (id) {
+	STR(IKEV2_PAYLOAD_NONE);
+	STR(IKEV2_PAYLOAD_SA);
+	STR(IKEV2_PAYLOAD_KE);
+	STR(IKEV2_PAYLOAD_IDi);
+	STR(IKEV2_PAYLOAD_IDr);
+	STR(IKEV2_PAYLOAD_CERT);
+	STR(IKEV2_PAYLOAD_CERTREQ);
+	STR(IKEV2_PAYLOAD_AUTH);
+	STR(IKEV2_PAYLOAD_NONCE);
+	STR(IKEV2_PAYLOAD_NOTIFY);
+	STR(IKEV2_PAYLOAD_DELETE);
+	STR(IKEV2_PAYLOAD_VENDOR);
+	STR(IKEV2_PAYLOAD_TSi);
+	STR(IKEV2_PAYLOAD_TSr);
+	STR(IKEV2_PAYLOAD_SK);
+	STR(IKEV2_PAYLOAD_CP);
+	STR(IKEV2_PAYLOAD_EAP);
+	STR(IKEV2_PAYLOAD_GSPM);
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_pay_short_str(ikev2_pay_type_t id)
+{
+	switch (id) {
+	case IKEV2_PAYLOAD_NONE:
+		return ("NONE");
+	case IKEV2_PAYLOAD_SA:
+		return ("SA");
+	case IKEV2_PAYLOAD_KE:
+		return ("KE");
+	case IKEV2_PAYLOAD_IDi:
+		return ("IDi");
+	case IKEV2_PAYLOAD_IDr:
+		return ("IDr");
+	case IKEV2_PAYLOAD_CERT:
+		return ("CERT");
+	case IKEV2_PAYLOAD_CERTREQ:
+		return ("CERTREQ");
+	case IKEV2_PAYLOAD_AUTH:
+		return ("AUTH");
+	case IKEV2_PAYLOAD_NONCE:
+		return ("No");
+	case IKEV2_PAYLOAD_NOTIFY:
+		return ("N");
+	case IKEV2_PAYLOAD_DELETE:
+		return ("D");
+	case IKEV2_PAYLOAD_VENDOR:
+		return ("V");
+	case IKEV2_PAYLOAD_TSi:
+		return ("TSi");
+	case IKEV2_PAYLOAD_TSr:
+		return ("TSr");
+	case IKEV2_PAYLOAD_SK:
+		return ("SK");
+	case IKEV2_PAYLOAD_CP:
+		return ("CP");
+	case IKEV2_PAYLOAD_EAP:
+		return ("EAP");
+	case IKEV2_PAYLOAD_GSPM:
+		return ("GSPM");
+	}
+	return ("UNKNOWN");
+}
+const char *
+ikev2_spi_str(ikev2_spi_proto_t id)
+{
+	switch (id) {
+	case IKEV2_PROTO_NONE:
+		return ("NONE");
+	case IKEV2_PROTO_IKE:
+		return ("IKE");
+	case IKEV2_PROTO_AH:
+		return ("AH");
+	case IKEV2_PROTO_ESP:
+		return ("ESP");
+	case IKEV2_PROTO_FC_ESP_HEADER:
+		return ("FC_ESP_HEADER");
+	case IKEV2_PROTO_FC_CT_AUTH:
+		return ("FC_CT_AUTH");
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_xf_type_str(ikev2_xf_type_t id)
+{
+	switch (id) {
+	case IKEV2_XF_ENCR:
+		return ("ENCR");
+	case IKEV2_XF_PRF:
+		return ("PRF");
+	case IKEV2_XF_AUTH:
+		return ("AUTH");
+	case IKEV2_XF_DH:
+		return ("DH");
+	case IKEV2_XF_ESN:
+		return ("ESN");
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_xf_encr_str(ikev2_xf_encr_t id)
+{
+	switch (id) {
+	STR(IKEV2_ENCR_NONE);
+	STR(IKEV2_ENCR_DES_IV64);
+	STR(IKEV2_ENCR_DES);
+	STR(IKEV2_ENCR_3DES);
+	STR(IKEV2_ENCR_RC5);
+	STR(IKEV2_ENCR_IDEA);
+	STR(IKEV2_ENCR_CAST);
+	STR(IKEV2_ENCR_BLOWFISH);
+	STR(IKEV2_ENCR_3IDEA);
+	STR(IKEV2_ENCR_DES_IV32);
+	STR(IKEV2_ENCR_RC4);
+	STR(IKEV2_ENCR_NULL);
+	STR(IKEV2_ENCR_AES_CBC);
+	STR(IKEV2_ENCR_AES_CTR);
+	STR(IKEV2_ENCR_AES_CCM_8);
+	STR(IKEV2_ENCR_AES_CCM_12);
+	STR(IKEV2_ENCR_AES_CCM_16);
+	STR(IKEV2_ENCR_AES_GCM_8);
+	STR(IKEV2_ENCR_AES_GCM_12);
+	STR(IKEV2_ENCR_AES_GCM_16);
+	STR(IKEV2_ENCR_NULL_AES_GMAC);
+	STR(IKEV2_ENCR_XTS_AES);
+	STR(IKEV2_ENCR_CAMELLIA_CBC);
+	STR(IKEV2_ENCR_CAMELLIA_CTR);
+	STR(IKEV2_ENCR_CAMELLIA_CCM_8);
+	STR(IKEV2_ENCR_CAMELLIA_CCM_12);
+	STR(IKEV2_ENCR_CAMELLIA_CCM_16);
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_xf_auth_str(ikev2_xf_auth_t id)
+{
+	switch (id) {
+	STR(IKEV2_XF_AUTH_NONE);
+	STR(IKEV2_XF_AUTH_HMAC_MD5_96);
+	STR(IKEV2_XF_AUTH_HMAC_SHA1_96);
+	STR(IKEV2_XF_AUTH_DES_MAC);
+	STR(IKEV2_XF_AUTH_KPDK_MD5);
+	STR(IKEV2_XF_AUTH_AES_XCBC_96);
+	STR(IKEV2_XF_AUTH_HMAC_MD5_128);
+	STR(IKEV2_XF_AUTH_HMAC_SHA1_160);
+	STR(IKEV2_XF_AUTH_AES_CMAC_96);
+	STR(IKEV2_XF_AUTH_AES_128_GMAC);
+	STR(IKEV2_XF_AUTH_AES_192_GMAC);
+	STR(IKEV2_XF_AUTH_AES_256_GMAC);
+	STR(IKEV2_XF_AUTH_HMAC_SHA2_256_128);
+	STR(IKEV2_XF_AUTH_HMAC_SHA2_384_192);
+	STR(IKEV2_XF_AUTH_HMAC_SHA2_512_256);
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_auth_type_str(ikev2_auth_type_t id)
+{
+	switch (id) {
+	STR(IKEV2_AUTH_NONE);
+	STR(IKEV2_AUTH_RSA_SIG);
+	STR(IKEV2_AUTH_SHARED_KEY_MIC);
+	STR(IKEV2_AUTH_DSS_SIG);
+	STR(IKEV2_AUTH_ECDSA_256);
+	STR(IKEV2_AUTH_ECDSA_384);
+	STR(IKEV2_AUTH_ECDSA_512);
+	STR(IKEV2_AUTH_GSPM);
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_dh_str(ikev2_dh_t id)
+{
+	switch (id) {
+	STR(IKEV2_DH_NONE);
+	STR(IKEV2_DH_MODP_768);
+	STR(IKEV2_DH_MODP_1024);
+	STR(IKEV2_DH_EC2N_155);
+	STR(IKEV2_DH_EC2N_185);
+	STR(IKEV2_DH_MODP_1536);
+	STR(IKEV2_DH_MODP_2048);
+	STR(IKEV2_DH_MODP_3072);
+	STR(IKEV2_DH_MODP_4096);
+	STR(IKEV2_DH_MODP_6144);
+	STR(IKEV2_DH_MODP_8192);
+	STR(IKEV2_DH_ECP_256);
+	STR(IKEV2_DH_ECP_384);
+	STR(IKEV2_DH_ECP_521);
+	STR(IKEV2_DH_MODP_1024_160);
+	STR(IKEV2_DH_MODP_2048_224);
+	STR(IKEV2_DH_MODP_2048_256);
+	STR(IKEV2_DH_ECP_192);
+	STR(IKEV2_DH_ECP_224);
+	STR(IKEV2_DH_BRAINPOOL_P224R1);
+	STR(IKEV2_DH_BRAINPOOL_P256R1);
+	STR(IKEV2_DH_BRAINPOOL_P384R1);
+	STR(IKEV2_DH_BRAINPOOL_P512R1);
+	}
+	return ("UNKNOWN");
+}
+
+const char *
+ikev2_notify_str(ikev2_notify_type_t id)
+{
+	switch (id) {
+	case IKEV2_N_UNSUPPORTED_CRITICAL_PAYLOAD:
+		return ("UNSUPPORTED_CRITICAL_PAYLOAD");
+	case IKEV2_N_INVALID_IKE_SPI:
+		return ("INVALID_IKE_SPI");
+	case IKEV2_N_INVALID_MAJOR_VERSION:
+		return ("INVALID_MAJOR_VERSION");
+	case IKEV2_N_INVALID_SYNTAX:
+		return ("INVALID_SYNTAX");
+	case IKEV2_N_INVALID_MESSAGE_ID:
+		return ("INVALID_MESSAGE_ID");
+	case IKEV2_N_INVALID_SPI:
+		return ("INVALID_SPI");
+	case IKEV2_N_NO_PROPOSAL_CHOSEN:
+		return ("NO_PROPOSAL_CHOSEN");
+	case IKEV2_N_INVALID_KE_PAYLOAD:
+		return ("INVALID_KE_PAYLOAD");
+	case IKEV2_N_AUTHENTICATION_FAILED:
+		return ("AUTHENTICATION_FAILED");
+	case IKEV2_N_SINGLE_PAIR_REQUIRED:
+		return ("SINGLE_PAIR_REQUIRED");
+	case IKEV2_N_NO_ADDITIONAL_SAS:
+		return ("NO_ADDITIONAL_SAS");
+	case IKEV2_N_INTERNAL_ADDRESS_FAILURE:
+		return ("INTERNAL_ADDRESS_FAILURE");
+	case IKEV2_N_FAILED_CP_REQUIRED:
+		return ("FAILED_CP_REQUIRED");
+	case IKEV2_N_TS_UNACCEPTABLE:
+		return ("TS_UNACCEPTABLE");
+	case IKEV2_N_INVALID_SELECTORS:
+		return ("INVALID_SELECTORS");
+	case IKEV2_N_UNACCEPTABLE_ADDRESSES:
+		return ("UNACCEPTABLE_ADDRESSES");
+	case IKEV2_N_UNEXPECTED_NAT_DETECTED:
+		return ("UNEXPECTED_NAT_DETECTED");
+	case IKEV2_N_USE_ASSIGNED_HoA:
+		return ("USE_ASSIGNED_HoA");
+	case IKEV2_N_TEMPORARY_FAILURE:
+		return ("TEMPORARY_FAILURE");
+	case IKEV2_N_CHILD_SA_NOT_FOUND:
+		return ("CHILD_SA_NOT_FOUND");
+	case IKEV2_N_INITIAL_CONTACT:
+		return ("INITIAL_CONTACT");
+	case IKEV2_N_SET_WINDOW_SIZE:
+		return ("SET_WINDOW_SIZE");
+	case IKEV2_N_ADDITIONAL_TS_POSSIBLE:
+		return ("ADDITIONAL_TS_POSSIBLE");
+	case IKEV2_N_IPCOMP_SUPPORTED:
+		return ("IPCOMP_SUPPORTED");
+	case IKEV2_N_NAT_DETECTION_SOURCE_IP:
+		return ("NAT_DETECTION_SOURCE_IP");
+	case IKEV2_N_NAT_DETECTION_DESTINATION_IP:
+		return ("NAT_DETECTION_DESTINATION_IP");
+	case IKEV2_N_COOKIE:
+		return ("COOKIE");
+	case IKEV2_N_USE_TRANSPORT_MODE:
+		return ("USE_TRANSPORT_MODE");
+	case IKEV2_N_HTTP_CERT_LOOKUP_SUPPORTED:
+		return ("HTTP_CERT_LOOKUP_SUPPORTED");
+	case IKEV2_N_REKEY_SA:
+		return ("REKEY_SA");
+	case IKEV2_N_ESP_TFC_PADDING_NOT_SUPPORTED:
+		return ("ESP_TFC_PADDING_NOT_SUPPORTED");
+	case IKEV2_N_NON_FIRST_FRAGMENTS_ALSO:
+		return ("NON_FIRST_FRAGMENTS_ALSO");
+	case IKEV2_N_MOBIKE_SUPPORTED:
+		return ("MOBIKE_SUPPORTED");
+	case IKEV2_N_ADDITIONAL_IP4_ADDRESS:
+		return ("ADDITIONAL_IP4_ADDRESS");
+	case IKEV2_N_ADDITIONAL_IP6_ADDRESS:
+		return ("ADDITIONAL_IP6_ADDRESS");
+	case IKEV2_N_NO_ADDITIONAL_ADDRESSES:
+		return ("NO_ADDITIONAL_ADDRESSES");
+	case IKEV2_N_UPDATE_SA_ADDRESSES:
+		return ("UPDATE_SA_ADDRESSES");
+	case IKEV2_N_COOKIE2:
+		return ("COOKIE2");
+	case IKEV2_N_NO_NATS_ALLOWED:
+		return ("NO_NATS_ALLOWED");
+	case IKEV2_N_AUTH_LIFETIME:
+		return ("AUTH_LIFETIME");
+	case IKEV2_N_MULTIPLE_AUTH_SUPPORTED:
+		return ("MULTIPLE_AUTH_SUPPORTED");
+	case IKEV2_N_ANOTHER_AUTH_FOLLOWS:
+		return ("ANOTHER_AUTH_FOLLOWS");
+	case IKEV2_N_REDIRECT_SUPPORTED:
+		return ("REDIRECT_SUPPORTED");
+	case IKEV2_N_REDIRECT:
+		return ("REDIRECT");
+	case IKEV2_N_REDIRECTED_FROM:
+		return ("REDIRECTED_FROM");
+	case IKEV2_N_TICKET_LT_OPAQUE:
+		return ("TICKET_LT_OPAQUE");
+	case IKEV2_N_TICKET_REQUEST:
+		return ("TICKET_REQUEST");
+	case IKEV2_N_TICKET_ACK:
+		return ("TICKET_ACK");
+	case IKEV2_N_TICKET_NACK:
+		return ("TICKET_NACK");
+	case IKEV2_N_TICKET_OPAQUE:
+		return ("TICKET_OPAQUE");
+	case IKEV2_N_LINK_ID:
+		return ("LINK_ID");
+	case IKEV2_N_USE_WESP_MODE:
+		return ("USE_WESP_MODE");
+	case IKEV2_N_ROHC_SUPPORTED:
+		return ("ROHC_SUPPORTED");
+	case IKEV2_N_EAP_ONLY_AUTHENTICATION:
+		return ("EAP_ONLY_AUTHENTICATION");
+	case IKEV2_N_CHILDLESS_IKEV2_SUPPORTED:
+		return ("CHILDLESS_IKEV2_SUPPORTED");
+	case IKEV2_N_QUICK_CRASH_DETECTION:
+		return ("QUICK_CRASH_DETECTION");
+	case IKEV2_N_IKEV2_MESSAGE_ID_SYNC_SUPPORTED:
+		return ("IKEV2_MESSAGE_ID_SYNC_SUPPORTED");
+	case IKEV2_N_IPSEC_REPLAY_CTR_SYNC_SUPPORTED:
+		return ("IPSEC_REPLAY_CTR_SYNC_SUPPORTED");
+	case IKEV2_N_IKEV2_MESSAGE_ID_SYNC:
+		return ("IKEV2_MESSAGE_ID_SYNC");
+	case IKEV2_N_IPSEC_REPLAY_CTR_SYNC:
+		return ("IPSEC_REPLAY_CTR_SYNC");
+	case IKEV2_N_SECURE_PASSWORD_METHODS:
+		return ("SECURE_PASSWORD_METHODS");
+	case IKEV2_N_PSK_PERSIST:
+		return ("PSK_PERSIST");
+	case IKEV2_N_PSK_CONFIRM:
+		return ("PSK_CONFIRM");
+	case IKEV2_N_ERX_SUPPORTED:
+		return ("ERX_SUPPORTED");
+	case IKEV2_N_IFOM_CAPABILITY:
+		return ("IFOM_CAPABILITY");
+	}
+	return ("UNKNOWN");
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_enum.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,40 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _IKEV2_ENUM_H
+#define	_IKEV2_ENUM_H
+
+#include "ikev2.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+const char *ikev2_exch_str(ikev2_exch_t);
+const char *ikev2_pay_str(ikev2_pay_type_t);
+const char *ikev2_pay_short_str(ikev2_pay_type_t);
+const char *ikev2_spi_str(ikev2_spi_proto_t);
+const char *ikev2_xf_type_str(ikev2_xf_type_t);
+const char *ikev2_xf_encr_str(ikev2_xf_encr_t);
+const char *ikev2_xf_auth_str(ikev2_xf_auth_t);
+const char *ikev2_auth_type_str(ikev2_auth_type_t);
+const char *ikev2_dh_str(ikev2_dh_t);
+const char *ikev2_notify_str(ikev2_notify_type_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKEV2_ENUM_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_pkt.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,1407 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#include <stddef.h>
+#include <assert.h>
+#include <umem.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <netinet/in.h>
+#include <security/cryptoki.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <pthread.h>
+#include <sys/debug.h>
+#include <note.h>
+#include <stdarg.h>
+#include <alloca.h>
+#include "defs.h"
+#include "pkt_impl.h"
+#include "ikev2.h"
+#include "ikev2_sa.h"
+#include "ikev2_pkt.h"
+#include "ikev2_enum.h"
+#include "pkcs11.h"
+#include "random.h"
+
+#define	PKT_IS_V2(p) \
+	(IKE_GET_MAJORV((p)->header.version) == IKE_GET_MAJORV(IKEV2_VERSION))
+
+/* Allocate an outbound IKEv2 pkt for an initiator of the given exchange type */
+pkt_t *
+ikev2_pkt_new_initiator(ikev2_sa_t *i2sa, ikev2_exch_t exch_type)
+{
+	pkt_t *pkt;
+
+	pkt = pkt_out_alloc(I2SA_LOCAL_SPI(i2sa),
+	    I2SA_REMOTE_SPI(i2sa),
+	    IKEV2_VERSION,
+	    exch_type, 0);
+	if (pkt == NULL)
+		return (NULL);
+
+	pkt->pkt_header.flags = IKEV2_FLAG_INITIATOR;
+	return (pkt);
+}
+
+/* Allocate a ikev2_pkt_t for an IKEv2 outbound response */
+pkt_t *
+ikev2_pkt_new_response(const pkt_t *init)
+{
+	pkt_t *pkt;
+
+	ASSERT(PKT_IS_V2(init));
+
+	pkt = pkt_out_alloc(init->pkt_header.initiator_spi,
+	    init->pkt_header.responder_spi,
+	    IKEV2_VERSION,
+	    init->pkt_header.exch_type,
+	    init->pkt_header.msgid);
+	if (pkt == NULL)
+		return (NULL);
+
+	pkt->pkt_header.flags = IKEV2_FLAG_RESPONSE;
+	return (pkt);
+}
+
+struct validate_data {
+	pkt_t		*pkt;
+	size_t		notify_count;
+	size_t		payload_count[IKEV2_NUM_PAYLOADS];
+	boolean_t	initiator;
+	uint8_t		exch_type;
+};
+
+static pkt_walk_ret_t check_payload(uint8_t, uint8_t, uchar_t *restrict,
+    size_t, void *restrict);
+static boolean_t check_sa_init_payloads(boolean_t, const size_t *);
+
+/* Allocate a ikev2_pkt_t for an inbound datagram in raw */
+pkt_t *
+ikev2_pkt_new_inbound(uchar_t *buf, size_t buflen)
+{
+	const ike_header_t	*hdr = NULL;
+	pkt_t			*pkt = NULL;
+	size_t			*counts = NULL;
+	struct validate_data	arg = { 0 };
+	size_t			i = 0;
+
+	ASSERT(IS_P2ALIGNED(buf, sizeof (uint64_t)));
+
+	hdr = (const ike_header_t *)buf;
+
+	ASSERT(IKE_GET_MAJORV(hdr->version) == IKE_GET_MAJORV(IKEV2_VERSION));
+
+	/*
+	 * Make sure either the initiator or response flag is set, but
+	 * not both.
+	 */
+	uint8_t flags = hdr->flags & (IKEV2_FLAG_INITIATOR|IKEV2_FLAG_RESPONSE);
+	if ((flags ^ (IKEV2_FLAG_INITIATOR|IKEV2_FLAG_RESPONSE)) == 0) {
+		/* XXX: log msg? */
+		return (NULL);
+	}
+
+	if ((pkt = pkt_in_alloc(buf, buflen)) == NULL) {
+		/* XXX: log msg */
+		return (NULL);
+	}
+
+	arg.pkt = pkt;
+	arg.exch_type = hdr->exch_type;
+	arg.initiator = !!(hdr->flags & IKEV2_FLAG_INITIATOR);
+
+	if (pkt_payload_walk((uchar_t *)(hdr + 1),
+	    buflen - sizeof (ike_header_t), check_payload, hdr->next_payload,
+	    &arg) != PKT_WALK_OK)
+		goto discard;
+
+	counts = arg.payload_count;
+
+#define	PAYCOUNT(totals, paytype) totals[(paytype) - IKEV2_PAYLOAD_MIN]
+
+	switch (arg.exch_type) {
+	case IKEV2_EXCH_IKE_AUTH:
+	case IKEV2_EXCH_CREATE_CHILD_SA:
+	case IKEV2_EXCH_INFORMATIONAL:
+		/* check_payload() already made sure we only have SK payloads */
+		if (PAYCOUNT(counts, IKEV2_PAYLOAD_SK) == 1)
+			return (pkt);
+
+		/* XXX: log */
+		goto discard;
+	case IKEV2_EXCH_IKE_SA_INIT:
+		break;
+	case IKEV2_EXCH_IKE_SESSION_RESUME:
+		INVALID("arg->exch_type");
+		break;
+	}
+
+#define	HAS_NOTIFY(totals) (!!(PAYCOUNT(totals, IKEV2_PAYLOAD_NOTIFY) > 0))
+
+	for (i = IKEV2_PAYLOAD_MIN; i <= IKEV2_PAYLOAD_MAX; i++) {
+		size_t count = PAYCOUNT(counts, i);
+
+		switch (i) {
+		/* Never allowed in an SA_INIT exchange */
+		case IKEV2_PAYLOAD_IDi:
+		case IKEV2_PAYLOAD_IDr:
+		case IKEV2_PAYLOAD_CERT:
+		case IKEV2_PAYLOAD_AUTH:
+		case IKEV2_PAYLOAD_DELETE:
+		case IKEV2_PAYLOAD_TSi:
+		case IKEV2_PAYLOAD_TSr:
+		case IKEV2_PAYLOAD_SK:
+		case IKEV2_PAYLOAD_CP:
+		case IKEV2_PAYLOAD_EAP:
+		case IKEV2_PAYLOAD_GSPM:
+			if (count > 0) {
+				/* XXX: log */
+				goto discard;
+			}
+			break;
+
+		/* can appear 0 or more times */
+		case IKEV2_PAYLOAD_NOTIFY:
+		case IKEV2_PAYLOAD_CERTREQ:
+			break;
+
+		case IKEV2_PAYLOAD_VENDOR:
+			if (PAYCOUNT(counts, IKEV2_PAYLOAD_SA) > 0 ||
+			    PAYCOUNT(counts, IKEV2_PAYLOAD_NOTIFY) > 0)
+				break;
+
+		case IKEV2_PAYLOAD_SA:
+			if (count != 1 && !HAS_NOTIFY(counts)) {
+				/* XXX: log */
+				goto discard;
+			}
+			break;
+
+		case IKEV2_PAYLOAD_KE:
+		case IKEV2_PAYLOAD_NONCE:
+			if (count != 1) {
+				if (!HAS_NOTIFY(counts)) {
+					/* XXX: log */
+					goto discard;
+				}
+				break;
+			}
+			if (PAYCOUNT(counts, IKEV2_PAYLOAD_SA) != 1) {
+				/* XXX: log */
+				goto discard;
+			}
+			break;
+		}
+	}
+
+	return (pkt);
+
+discard:
+	pkt_free(pkt);
+	return (NULL);
+#undef PAYCOUNT
+#undef HAS_NOTIFY
+}
+
+/*
+ * Cache the payload offsets and do some minimal checking.
+ * By virtue of walking the payloads, we also validate the payload
+ * lengths do not overflow or underflow
+ */
+static pkt_walk_ret_t
+check_payload(uint8_t paytype, uint8_t resv, uchar_t *restrict buf,
+    size_t buflen, void *restrict cookie)
+{
+	struct validate_data *arg = (struct validate_data *)cookie;
+	boolean_t critical = !!(resv & IKEV2_CRITICAL_PAYLOAD);
+
+	/* Skip unknown payloads.  We will check the critical bit later */
+	if (paytype < IKEV2_PAYLOAD_MIN || paytype > IKEV2_PAYLOAD_MAX)
+		return (PKT_WALK_OK);
+
+	switch (arg->exch_type) {
+	case IKEV2_EXCH_IKE_AUTH:
+	case IKEV2_EXCH_CREATE_CHILD_SA:
+	case IKEV2_EXCH_INFORMATIONAL:
+		/*
+		 * All payloads in these exchanges should be encrypted
+		 * at this early stage.  RFC 5996 isn't quite clear
+		 * what to do.  There seem to be three possibilities:
+		 *
+		 * 1. Drop the packet with no further action.
+		 * 2. IFF the encrypted payload's integrity check passes,
+		 *    and the packet is an initiator, send an INVALID_SYNTAX
+		 *    notification in response.  Otherwise, drop the packet
+		 * 3. Ignore the unencrypted payloads and only process the
+		 *    payloads that passed the integrity check.
+		 *
+		 * As RFC5996 suggests committing minimal CPU state until
+		 * a valid request is present (to help mitigate DOS attacks),
+		 * option 2 would still commit us to performing potentially
+		 * expensive decryption and authentication calculations.
+		 * Option 3 would require us to track which payloads were
+		 * authenticated and which were not.  Since some payloads
+		 * (e.g. notify) can appear multiple times in a packet
+		 * (requiring some sort of iteration to deal with them),
+		 * this seems potentially complicated and prone to potential
+		 * exploit.  Thus we opt for the simple solution of dropping
+		 * the packet.
+		 *
+		 * NOTE: if we successfully authenticate and decrypt a
+		 * packet for one of these exchanges and the decrypted
+		 * and authenticated payloads have range or value issues,
+		 * we may opt at that point to send an INVALID_SYNTAX
+		 * notification, but not here.
+		 */
+		if (paytype != IKEV2_PAYLOAD_SK) {
+			/* XXX: log message? */
+			return (PKT_WALK_ERROR);
+		}
+		return (PKT_WALK_OK);
+
+	case IKEV2_EXCH_IKE_SA_INIT:
+		break;
+
+	default:
+		/* Unknown exchange, bail */
+		/* XXX: log message? */
+		return (PKT_WALK_ERROR);
+	}
+
+	ASSERT3U(exch_type, ==, IKEV2_EXCH_SA_INIT);
+
+	arg->payload_count[paytype - IKEV2_PAYLOAD_MIN]++;
+
+	if (paytype == IKEV2_PAYLOAD_NOTIFY) {
+		pkt_notify_t *ntfyp = pkt_notify(arg->pkt, arg->notify_count++);
+		ikev2_notify_t ntfy = { 0 };
+		size_t len = sizeof (ntfy);
+
+		if (buflen < len) {
+			/* XXX: log */
+			return (PKT_WALK_ERROR);
+		}
+		(void) memcpy(&ntfy, buf, sizeof (ntfy));
+		len += ntfy.n_spisize;
+		if (buflen < len) {
+			/* XXX: log */
+			return (PKT_WALK_ERROR);
+		}
+
+		ntfyp->pn_ptr = buf;
+		ntfyp->pn_type = ntohs(ntfy.n_type);
+		ntfyp->pn_len = buflen;
+		return (PKT_WALK_OK);
+	}
+
+	return (PKT_WALK_OK);
+}
+
+#define	PAYBIT(pay) ((uint32_t)1 << ((pay) - IKEV2_PAYLOAD_MIN))
+static const uint32_t multi_payloads =
+	PAYBIT(IKEV2_PAYLOAD_NOTIFY) |
+	PAYBIT(IKEV2_PAYLOAD_VENDOR) |
+	PAYBIT(IKEV2_PAYLOAD_CERTREQ);
+#define	IS_MULTI(pay) (!!(multi_payloads & PAYBIT(pay)))
+
+static struct payinfo {
+	uint32_t	required;
+	uint32_t	optional;
+} sa_init_info[] = {
+	{
+		/* required */
+		PAYBIT(IKEV2_PAYLOAD_SA) |
+		PAYBIT(IKEV2_PAYLOAD_KE) |
+		PAYBIT(IKEV2_PAYLOAD_NONCE),
+		/* optional */
+		PAYBIT(IKEV2_PAYLOAD_NOTIFY) |
+		PAYBIT(IKEV2_PAYLOAD_VENDOR) |
+		PAYBIT(IKEV2_PAYLOAD_CERTREQ)
+	},
+	{
+		/* required */
+		PAYBIT(IKEV2_PAYLOAD_NOTIFY),
+		/* optional */
+		PAYBIT(IKEV2_PAYLOAD_VENDOR)
+	}
+};
+
+void
+ikev2_pkt_free(pkt_t *pkt)
+{
+	pkt_free(pkt);
+}
+
+static void
+ikev2_add_payload(pkt_t *pkt, ikev2_pay_type_t ptype, boolean_t critical)
+{
+	uchar_t *payptr;
+	uint8_t resv = 0;
+
+	ASSERT(IKEV2_VALID_PAYLOAD(ptype));
+	ASSERT3U(pkt_write_left(pkt), >=, sizeof (ikev2_payload_t));
+
+	if (critical)
+		resv |= IKEV2_CRITICAL_PAYLOAD;
+
+	pkt_add_payload(pkt, ptype, resv);
+}
+
+boolean_t
+ikev2_add_sa(pkt_t *pkt)
+{
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t))
+		return (B_FALSE);
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_SA, B_FALSE);
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_prop(pkt_t *pkt, uint8_t propnum, ikev2_spi_proto_t proto,
+    uint64_t spi)
+{
+	size_t spilen;
+
+	switch (proto) {
+	case IKEV2_PROTO_AH:
+	case IKEV2_PROTO_ESP:
+		spilen = sizeof (uint32_t);
+		break;
+	case IKEV2_PROTO_IKE:
+		spilen == (spi == 0) ? 0 : sizeof (uint64_t);
+		break;
+	case IKEV2_PROTO_NONE:
+	case IKEV2_PROTO_FC_ESP_HEADER:
+	case IKEV2_PROTO_FC_CT_AUTH:
+		INVALID("proto");
+		break;
+	}
+
+	return (pkt_add_prop(pkt, propnum, proto, spilen, spi));
+}
+
+boolean_t
+ikev2_add_xform(pkt_t *pkt, ikev2_xf_type_t xftype, int xfid)
+{
+	return (pkt_add_xform(pkt, xftype, xfid));
+}
+
+boolean_t
+ikev2_add_xf_attr(pkt_t *pkt, ikev2_xf_attr_type_t xf_attr_type,
+    uintptr_t arg)
+{
+	switch (xf_attr_type) {
+	case IKEV2_XF_ATTR_KEYLEN:
+		ASSERT3U(arg, <, 0x10000);
+		return (pkt_add_xform_attr_tv(pkt, IKEV2_XF_ATTR_KEYLEN,
+		    (uint16_t)arg));
+	}
+
+	return (B_FALSE);
+}
+
+boolean_t
+ikev2_add_xf_encr(pkt_t *pkt, ikev2_xf_encr_t encr, uint16_t minbits,
+    uint16_t maxbits)
+{
+	uint16_t incr = 0;
+	boolean_t ok = B_TRUE;
+
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL:
+		INVALID("encr");
+		/*NOTREACHED*/
+		return (B_FALSE);
+
+	/* XXX: need to confirm this */
+	case IKEV2_ENCR_NULL_AES_GMAC:
+		return (B_TRUE);
+
+	/* ones that should never include a key size */
+	case IKEV2_ENCR_DES_IV64:
+	case IKEV2_ENCR_DES:
+	case IKEV2_ENCR_3DES:
+	case IKEV2_ENCR_IDEA:
+	case IKEV2_ENCR_3IDEA:
+	case IKEV2_ENCR_DES_IV32:
+		VERIFY3U(minbits, ==, 0);
+		VERIFY3U(maxbits, ==, 0);
+		return (ikev2_add_xform(pkt, IKEV2_XF_ENCR, encr));
+
+	/* optional key size */
+	case IKEV2_ENCR_RC4:
+	case IKEV2_ENCR_RC5:
+	case IKEV2_ENCR_BLOWFISH:
+	case IKEV2_ENCR_CAST:
+		if (minbits == 0 && maxbits == 0)
+			return (ikev2_add_xform(pkt, IKEV2_XF_ENCR, encr));
+		incr = 1;
+		break;
+
+	case IKEV2_ENCR_AES_CBC:
+	case IKEV2_ENCR_AES_CTR:
+	case IKEV2_ENCR_AES_CCM_8:
+	case IKEV2_ENCR_AES_CCM_12:
+	case IKEV2_ENCR_AES_CCM_16:
+	case IKEV2_ENCR_AES_GCM_8:
+	case IKEV2_ENCR_AES_GCM_12:
+	case IKEV2_ENCR_AES_GCM_16:
+	case IKEV2_ENCR_XTS_AES:
+		incr = 64;
+		break;
+
+	case IKEV2_ENCR_CAMELLIA_CBC:
+	case IKEV2_ENCR_CAMELLIA_CTR:
+	case IKEV2_ENCR_CAMELLIA_CCM_8:
+	case IKEV2_ENCR_CAMELLIA_CCM_12:
+	case IKEV2_ENCR_CAMELLIA_CCM_16:
+		VERIFY3U(minbits, >=, 128);
+		VERIFY3U(maxbits, <=, 256);
+		incr = 64;
+		break;
+	}
+
+	if (incr == 1) {
+		/*
+		 * instead of adding potentially hundreds of transforms for
+		 * a range of keysizes, for those with arbitrary key sizes
+		 * we just add the min and max
+		 */
+		if (minbits != maxbits) {
+			ok &= ikev2_add_xform(pkt, IKEV2_XF_ENCR, encr);
+			ok &= ikev2_add_xf_attr(pkt, IKEV2_XF_ATTR_KEYLEN,
+			    minbits);
+		}
+		ok &= ikev2_add_xform(pkt, IKEV2_XF_ENCR, encr);
+		ok &= ikev2_add_xf_attr(pkt, IKEV2_XF_ATTR_KEYLEN, maxbits);
+		return (ok);
+	}
+
+	for (size_t bits = minbits; bits <= maxbits; bits += incr) {
+		ok &= ikev2_add_xform(pkt, IKEV2_XF_ENCR, encr);
+		ok &= ikev2_add_xf_attr(pkt, IKEV2_XF_ATTR_KEYLEN, bits);
+	}
+
+	return (ok);
+}
+
+boolean_t
+ikev2_add_ke(pkt_t *restrict pkt, uint_t group,
+    const uchar_t *restrict data, size_t len)
+{
+	ikev2_ke_t	ke = { 0 };
+
+	ASSERT3U(group, <, 0x10000);
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + sizeof (ke) + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_KE, B_FALSE);
+	ke.kex_dhgroup = htons((uint16_t)group);
+	PKT_APPEND_STRUCT(pkt, ke);
+	PKT_APPEND_DATA(pkt, data, len);
+	return (B_TRUE);
+}
+
+static boolean_t
+ikev2_add_id_common(pkt_t *restrict pkt, boolean_t id_i, ikev2_id_type_t idtype,
+    va_list ap)
+{
+	ikev2_id_t		id = { 0 };
+	ikev2_pay_type_t 	paytype =
+	    (id_i) ? IKEV2_PAYLOAD_IDi : IKEV2_PAYLOAD_IDr;
+	const uchar_t		*data;
+	size_t			len = 0;
+
+	data = va_arg(ap, const uchar_t *);
+
+	switch (idtype) {
+	case IKEV2_ID_IPV4_ADDR:
+		len = sizeof (in_addr_t);
+		break;
+	case IKEV2_ID_FQDN:
+	case IKEV2_ID_RFC822_ADDR:
+		len = strlen((const char *)data);
+		break;
+	case IKEV2_ID_IPV6_ADDR:
+		len = sizeof (in6_addr_t);
+		break;
+	case IKEV2_ID_DER_ASN1_DN:
+	case IKEV2_ID_DER_ASN1_GN:
+	case IKEV2_ID_KEY_ID:
+		len = va_arg(ap, size_t);
+		break;
+	case IKEV2_ID_FC_NAME:
+		INVALID("idtype");
+		break;
+	}
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + sizeof (id) + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, paytype, B_FALSE);
+	id.id_type = (uint8_t)idtype;
+	PKT_APPEND_STRUCT(pkt, id);
+	PKT_APPEND_DATA(pkt, data, len);
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_id_i(pkt_t *restrict pkt, ikev2_id_type_t idtype, ...)
+{
+	va_list ap;
+	boolean_t ret;
+
+	va_start(ap, idtype);
+	ret = ikev2_add_id_common(pkt, B_TRUE, idtype, ap);
+	va_end(ap);
+	return (ret);
+}
+
+boolean_t
+ikev2_add_id_r(pkt_t *restrict pkt, ikev2_id_type_t idtype, ...)
+{
+	va_list ap;
+	boolean_t ret;
+
+	va_start(ap, idtype);
+	ret = ikev2_add_id_common(pkt, B_FALSE, idtype, ap);
+	va_end(ap);
+	return (ret);
+}
+
+static boolean_t ikev2_add_cert_common(pkt_t *restrict, boolean_t,
+    ikev2_cert_t, const uchar_t *, size_t);
+
+boolean_t
+ikev2_add_cert(pkt_t *restrict pkt, ikev2_cert_t cert_type, const uchar_t *cert,
+    size_t len)
+{
+	return (ikev2_add_cert_common(pkt, B_TRUE, cert_type, cert, len));
+}
+
+boolean_t
+ikev2_add_certreq(pkt_t *restrict pkt, ikev2_cert_t cert_type,
+    const uchar_t *cert, size_t len)
+{
+	return (ikev2_add_cert_common(pkt, B_FALSE, cert_type, cert, len));
+}
+
+static boolean_t
+ikev2_add_cert_common(pkt_t *restrict pkt, boolean_t cert, ikev2_cert_t type,
+    const uchar_t *restrict data, size_t len)
+{
+	ikev2_pay_type_t ptype =
+	    (cert) ? IKEV2_PAYLOAD_CERT : IKEV2_PAYLOAD_CERTREQ;
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + 1 + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, ptype, B_FALSE);
+	return (pkt_add_cert(pkt, (uint8_t)type, data, len));
+}
+
+boolean_t
+ikev2_add_auth(pkt_t *restrict pkt, ikev2_auth_type_t auth_method,
+    const uchar_t *restrict data, size_t len)
+{
+	ikev2_auth_t auth = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + sizeof (auth) +
+	    len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_AUTH, B_FALSE);
+	auth.auth_method = (uint8_t)auth_method;
+	PKT_APPEND_STRUCT(pkt, auth);
+	PKT_APPEND_DATA(pkt, data, len);
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_nonce(pkt_t *restrict pkt, size_t len)
+{
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_NONCE, B_FALSE);
+	random_high(pkt->pkt_ptr, len);
+	pkt->pkt_ptr += len;
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_notify(pkt_t *restrict pkt, ikev2_spi_proto_t proto, uint64_t spi,
+    ikev2_notify_type_t ntfy_type, const void *restrict data, size_t len)
+{
+	ikev2_notify_t ntfy = { 0 };
+	size_t spisize = 0;
+
+	switch (proto) {
+	case IKEV2_PROTO_NONE:
+	case IKEV2_PROTO_FC_ESP_HEADER:
+	case IKEV2_PROTO_FC_CT_AUTH:
+		INVALID("proto");
+		/*NOTREACHED*/
+		return (B_FALSE);
+	case IKEV2_PROTO_IKE:
+		if (spi != 0)
+			spisize == sizeof (uint64_t);
+		break;
+	case IKEV2_PROTO_AH:
+	case IKEV2_PROTO_ESP:
+		spisize = sizeof (uint32_t);
+		VERIFY3U(spi, <=, UINT32_MAX);
+		VERIFY3U(spi, !=, 0);
+		break;
+	}
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + sizeof (ntfy) +
+	    spisize + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_NOTIFY, B_FALSE);
+	ntfy.n_protoid = proto;
+	ntfy.n_spisize = spisize;
+	ntfy.n_type = htons((uint16_t)ntfy_type);
+	PKT_APPEND_STRUCT(pkt, ntfy);
+
+	switch (spisize) {
+	case 0:
+		break;
+	case sizeof (uint32_t):
+		put32(pkt, htonl((uint32_t)spi));
+		break;
+	case sizeof (uint64_t):
+		put64(pkt, htonll(spi));
+		break;
+	default:
+		INVALID(spisize);
+	}
+
+	if (data != NULL)
+		PKT_APPEND_DATA(pkt, data, len);
+
+	return (B_TRUE);
+}
+
+static boolean_t delete_finish(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+
+boolean_t
+ikev2_add_delete(pkt_t *pkt, ikev2_spi_proto_t proto)
+{
+	ikev2_delete_t del = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) +
+	    sizeof (ikev2_delete_t))
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_DELETE, B_FALSE);
+	pkt_stack_push(pkt, PSI_DEL, delete_finish, 0);
+
+	del.del_protoid = (uint8_t)proto;
+	switch (proto) {
+	case IKEV2_PROTO_IKE:
+		del.del_spisize = 0;
+		break;
+	case IKEV2_PROTO_AH:
+	case IKEV2_PROTO_ESP:
+		del.del_spisize = sizeof (uint32_t);
+		break;
+	case IKEV2_PROTO_NONE:
+	case IKEV2_PROTO_FC_ESP_HEADER:
+	case IKEV2_PROTO_FC_CT_AUTH:
+		INVALID("proto");
+	}
+
+	PKT_APPEND_STRUCT(pkt, del);
+	return (B_TRUE);
+}
+
+static boolean_t
+delete_finish(pkt_t *restrict pkt, uchar_t *restrict buf, uintptr_t swaparg,
+    size_t numspi)
+{
+	ikev2_delete_t	del = { 0 };
+
+	ASSERT3U(numspi, <, 0x10000);
+
+	(void) memcpy(&del, buf, sizeof (del));
+	del.del_nspi = htons((uint16_t)numspi);
+	(void) memcpy(buf, &del, sizeof (del));
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_vendor(pkt_t *restrict pkt, const void *restrict vid, size_t len)
+{
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) + len)
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_VENDOR, B_FALSE);
+	PKT_APPEND_DATA(pkt, vid, len);
+	return (B_TRUE);
+}
+
+static boolean_t add_ts_common(pkt_t *, boolean_t);
+
+boolean_t
+ikev2_add_ts_i(pkt_t *restrict pkt)
+{
+	return (add_ts_common(pkt, B_TRUE));
+}
+
+boolean_t
+ikev2_add_ts_r(pkt_t *restrict pkt)
+{
+	return (add_ts_common(pkt, B_FALSE));
+}
+
+static boolean_t ts_finish(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+
+static boolean_t
+add_ts_common(pkt_t *pkt, boolean_t ts_i)
+{
+	ikev2_ts_t ts = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (ikev2_payload_t) +
+	    sizeof (ikev2_ts_t))
+		return (B_FALSE);
+
+	ikev2_add_payload(pkt, (ts_i) ? IKEV2_PAYLOAD_TSi : IKEV2_PAYLOAD_TSr,
+	    B_FALSE);
+	pkt_stack_push(pkt, PSI_TSP, ts_finish, 0);
+	PKT_APPEND_STRUCT(pkt, ts);
+	return (B_TRUE);
+}
+
+static boolean_t
+ts_finish(pkt_t *restrict pkt, uchar_t *restrict buf, uintptr_t swaparg,
+    size_t numts)
+{
+	ikev2_tsp_t	ts = { 0 };
+
+	ASSERT3U(numts, <, 0x100);
+
+	(void) memcpy(&ts, buf, sizeof (ts));
+	ts.tsp_count = (uint8_t)numts;
+	(void) memcpy(buf, &ts, sizeof (ts));
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_ts(pkt_t *restrict pkt, ikev2_ts_type_t type, uint8_t ip_proto,
+    const sockaddr_u_t *restrict start, const sockaddr_u_t *restrict end)
+{
+	ikev2_ts_t	ts = { 0 };
+	void		*startptr = NULL, *endptr = NULL;
+	size_t		len = 0;
+
+	ASSERT3U(ip_proto, <, 0x100);
+	ASSERT3U(start->sau_ss->ss_family, ==, end->sau_ss->ss_family);
+
+	pkt_stack_push(pkt, PSI_TS, 0, NULL);
+
+	ts.ts_length = sizeof (ts);
+	ts.ts_type = (uint8_t)type;
+
+	switch (type) {
+	case IKEV2_TS_IPV4_ADDR_RANGE:
+		ASSERT3U(start->sau_ss->ss_family, ==, AF_INET);
+		ASSERT3U(end->sau_ss->ss_family, ==, AF_INET);
+		ts.ts_startport = start->sau_sin->sin_port;
+		ts.ts_endport = end->sau_sin->sin_port;
+		startptr = &start->sau_sin->sin_addr;
+		endptr = &end->sau_sin->sin_addr;
+		len = sizeof (in_addr_t);
+		ts.ts_length += 2 * len;
+		break;
+	case IKEV2_TS_IPV6_ADDR_RANGE:
+		ASSERT3U(start->sau_ss->ss_family, ==, AF_INET6);
+		ASSERT3U(end->sau_ss->ss_family, ==, AF_INET6);
+		ts.ts_startport = start->sau_sin6->sin6_port;
+		ts.ts_endport = end->sau_sin6->sin6_port;
+		startptr = &start->sau_sin6->sin6_addr;
+		endptr = &end->sau_sin6->sin6_addr;
+		len = sizeof (in6_addr_t);
+		ts.ts_length += 2 * len;
+		break;
+	case IKEV2_TS_FC_ADDR_RANGE:
+		INVALID("type");
+	}
+
+	if (pkt_write_left(pkt) < ts.ts_length)
+		return (B_FALSE);
+
+	ts.ts_protoid = ip_proto;
+	ts.ts_length = htons(ts.ts_length);
+	PKT_APPEND_STRUCT(pkt, ts);
+	PKT_APPEND_DATA(pkt, startptr, len);
+	PKT_APPEND_DATA(pkt, endptr, len);
+	return (B_TRUE);
+}
+
+static boolean_t encrypt_payloads(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+static boolean_t cbc_iv(pkt_t *restrict, uchar_t *);
+
+boolean_t
+ikev2_add_sk(pkt_t *restrict pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	size_t len = sizeof (ikev2_payload_t);
+	size_t ivlen = ikev2_encr_iv_size(sa->encr);
+	boolean_t ret;
+
+	len += ivlen;
+	len += ikev2_auth_icv_size(sa->encr, sa->auth);
+	len += ikev2_encr_block_size(sa->encr);
+
+	if (pkt_write_left(pkt) < len)
+		return (B_FALSE);
+
+	/*
+	 * This needs to happen first so that subsequent payloads are
+	 * encapsulated by the SK payload
+	 */
+	pkt_stack_push(pkt, PSI_SK, encrypt_payloads, 0);
+	ikev2_add_payload(pkt, IKEV2_PAYLOAD_SK, B_FALSE);
+
+	/*
+	 * Skip over space for IV, encrypt_payloads() will fill it in.
+	 * The memset() shouldn't be needed, as the memory should already be
+	 * 0-filled, but erring on the side of caution.
+	 */
+	(void) memset(pkt->pkt_ptr, 0, ivlen);
+	pkt->pkt_ptr += ivlen;
+	return (B_TRUE);
+}
+
+/*
+ * Based on recommendation from NIST 800-38A, Appendix C, use msgid
+ * which should be unique, encrypt using SK to generate IV
+ */
+static boolean_t
+cbc_iv(pkt_t *restrict pkt, uchar_t *ivp)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	CK_SESSION_HANDLE handle = p11h;
+	CK_MECHANISM mech;
+	CK_OBJECT_HANDLE key;
+	CK_RV rv;
+	CK_ULONG blocklen = 0; /* in bytes */
+
+	if (pkt->pkt_sa->flags & I2SA_INITIATOR)
+		key = sa->sk_ei;
+	else
+		key = sa->sk_er;
+
+	switch (pkt->pkt_sa->encr) {
+	case IKEV2_ENCR_AES_CBC:
+		mech.mechanism = CKM_AES_ECB;
+		mech.pParameter = NULL_PTR;
+		mech.ulParameterLen = 0;
+		blocklen = 16;
+		break;
+	case IKEV2_ENCR_CAMELLIA_CBC:
+		mech.mechanism = CKM_CAMELLIA_ECB;
+		mech.pParameter = NULL_PTR;
+		mech.ulParameterLen = 0;
+		blocklen = 16;
+		break;
+	default:
+		INVALID("encr");
+		/*NOTREACHED*/
+		return (B_FALSE);
+	}
+
+	if (pkt_write_left(pkt) < blocklen)
+		return (B_FALSE);
+
+	VERIFY3U(blocklen, >=, sizeof (uint32_t));
+
+	CK_ULONG buflen = blocklen;
+	uchar_t buf[blocklen];
+
+	(void) memset(buf, 0, blocklen);
+	(void) memcpy(buf, &pkt->pkt_header.msgid,
+	    sizeof (pkt->pkt_header.msgid));
+
+	rv = C_EncryptInit(handle, &mech, key);
+	if (rv != CKR_OK) {
+		PKCS11ERR(error, sa->i2sa_log, "C_EncryptInit", rv);
+		return (B_FALSE);
+	}
+
+	rv = C_Encrypt(handle, buf, blocklen, buf, &blocklen);
+	if (rv != CKR_OK) {
+		PKCS11ERR(error, sa->i2sa_log, "C_Encrypt", rv);
+		return (B_FALSE);
+	}
+
+	(void) memcpy(ivp, buf, ikev2_encr_iv_size(sa->encr));
+	return (B_TRUE);
+}
+
+static boolean_t
+crypt_common(pkt_t *pkt, boolean_t encrypt, uchar_t *iv, CK_ULONG ivlen,
+    uchar_t *data, CK_ULONG datalen, uchar_t *icv, CK_ULONG icvlen)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	const char *fn;
+	CK_SESSION_HANDLE handle = p11h;
+	CK_OBJECT_HANDLE key;
+	CK_MECHANISM mech;
+	union {
+		CK_AES_CTR_PARAMS	aes_ctr;
+		CK_CAMELLIA_CTR_PARAMS	cam_ctr;
+		CK_GCM_PARAMS		gcm;
+		CK_CCM_PARAMS		ccm;
+	} params;
+	CK_RV rc = 0;
+	encr_modes_t mode = ikev2_encr_mode(sa->encr);
+	uchar_t *nonce = NULL;
+	CK_ULONG noncelen = 0;
+
+	if (sa->flags & I2SA_INITIATOR)
+		key = pkt->pkt_sa->sk_ei;
+	else
+		key = pkt->pkt_sa->sk_er;
+
+	/*
+	 * For GCM and CCM, the nonce/IV used is a combination of both
+	 * the salt (derived from the PRF function along with the key)
+	 * and the transmitted 'IV' value.  This total value should be
+	 * 10-16 bytes at most, so the use of alloca() shouldn't present
+	 * any issues
+	 */
+	mech.mechanism = ikev2_encr_to_p11(sa->encr);
+	switch (mode) {
+	case MODE_NONE:
+		break;
+	case MODE_CBC:
+		mech.pParameter = iv;
+		mech.ulParameterLen = ivlen;
+		break;
+	case MODE_CTR:
+		/* TODO */
+		break;
+	case MODE_CCM:
+		noncelen = sa->saltlen + ivlen;
+		nonce = alloca(noncelen);
+		(void) memcpy(nonce, sa->salt, sa->saltlen);
+		(void) memcpy(nonce + sa->saltlen, iv, ivlen);
+		params.ccm.pAAD = (CK_BYTE_PTR)&pkt->pkt_raw;
+		params.ccm.ulAADLen = (CK_ULONG)(iv - params.ccm.pAAD);
+		params.ccm.ulMACLen = icvlen;
+		params.ccm.pNonce = nonce;
+		params.ccm.ulNonceLen = noncelen;
+		mech.pParameter = &params.ccm;
+		mech.ulParameterLen = sizeof (CK_CCM_PARAMS);
+		break;
+	case MODE_GCM:
+		noncelen = sa->saltlen + ivlen;
+		nonce = alloca(noncelen);
+		(void) memcpy(nonce, sa->salt, sa->saltlen);
+		(void) memcpy(nonce + sa->saltlen, iv, ivlen);
+		params.gcm.pIv = nonce;
+		params.gcm.ulIvLen = noncelen;
+		/*
+		 * There is a 'ulIvBits' field in CK_GCM_PARAMS.  This comes
+		 * straight from the published pkcs11t.h file.  However, it
+		 * does not appear to actually be used for anything, and looks
+		 * to be a leftover from the unpublished PKCS#11 v2.30 standard.
+		 * It is currently not set and ignored
+		 */
+		params.gcm.pAAD = (CK_BYTE_PTR)&pkt->pkt_raw;
+		params.gcm.ulAADLen = (CK_ULONG)(iv - params.gcm.pAAD);
+		params.gcm.ulTagBits = icvlen * 8;
+		mech.pParameter = &params.gcm;
+		mech.ulParameterLen = sizeof (CK_GCM_PARAMS);
+		break;
+	}
+
+	if (encrypt) {
+		rc = C_EncryptInit(handle, &mech, key);
+		fn = "C_EncryptInit";
+	} else {
+		rc = C_DecryptInit(handle, &mech, key);
+		fn = "C_DecryptInit";
+	}
+
+	if (rc != CKR_OK) {
+		PKCS11ERR(error, sa->i2sa_log, fn, rc);
+		goto done;
+	}
+
+	/*
+	 * XXX: the last parameter might need a separate var
+ 	 * because of extra room needed for ccm/gcm
+ 	 * ALSO: probably want a better error message for the combined mode
+ 	 * failures
+ 	 */
+	if (encrypt) {
+		rc = C_Encrypt(handle, data, datalen, data, &datalen);
+		fn = "C_Encrypt";
+	} else {
+		rc = C_Decrypt(handle, data, datalen, data, &datalen);
+		fn = "C_Decrypt";
+	}
+	if (rc != CKR_OK)
+		PKCS11ERR(error, sa->i2sa_log, fn, rc);
+
+done:
+	if (nonce != NULL)
+		(void) memset(nonce, 0, sizeof (nonce));
+	return ((rc == CKR_OK) ? B_TRUE : B_FALSE);
+}
+
+static boolean_t
+auth_common(pkt_t *pkt, boolean_t encrypt, uchar_t *icv, size_t icvlen)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	const char *fn = NULL;
+	CK_SESSION_HANDLE handle = p11h;
+	CK_OBJECT_HANDLE key;
+	CK_MECHANISM mech = { 0 };
+	CK_RV rc;
+	CK_BYTE_PTR data;
+	CK_ULONG datalen, len = icvlen;
+
+	if (sa->flags & I2SA_INITIATOR)
+		key = sa->sk_ai;
+	else
+		key = sa->sk_ar;
+
+	ASSERT3S(sa->auth, !=, IKEV2_XF_AUTH_NONE);
+	mech.mechanism = ikev2_auth_to_p11(sa->auth);
+	mech.pParameter = NULL_PTR;
+	mech.ulParameterLen = 0;
+
+	data = (CK_BYTE_PTR)&pkt->pkt_raw;
+	datalen = (CK_ULONG)(icv - data);
+
+	if (encrypt) {
+		rc = C_SignInit(handle, &mech, key);
+		fn = "C_SignInit";
+	} else {
+		rc = C_VerifyInit(handle, &mech, key);
+		fn = "C_SignInit";
+	}
+
+	if (rc != CKR_OK) {
+		PKCS11ERR(error, sa->i2sa_log, fn, rc);
+		return (B_FALSE);
+	}
+
+	if (encrypt) {
+		rc = C_Sign(handle, data, datalen, icv, &len);
+		fn = "C_Sign";
+	} else {
+		rc = C_Verify(handle, data, datalen, icv, len);
+		fn = "C_Verify";
+
+		/* give this one a better message */
+		if (rc == CKR_SIGNATURE_INVALID) {
+			bunyan_error(sa->i2sa_log, "integrity check failed",
+			    BUNYAN_T_END);
+			return (B_FALSE);
+		}
+	}
+
+	if (rc != CKR_OK)
+		PKCS11ERR(error, sa->i2sa_log, fn, rc);
+
+	return ((rc == CKR_OK) ? B_TRUE : B_FALSE);
+}
+
+static boolean_t
+encrypt_payloads(pkt_t *restrict pkt, uchar_t *restrict buf, uintptr_t swaparg,
+    size_t numencr)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	CK_BYTE_PTR iv, data, icv, nonce;
+	CK_ULONG ivlen, datalen, icvlen, noncelen, blocklen;
+	uint8_t padlen = 0;
+	encr_modes_t mode = ikev2_encr_mode(sa->encr);
+	
+	ivlen = ikev2_encr_iv_size(sa->encr);
+	icvlen = ikev2_auth_icv_size(sa->encr, sa->auth);
+	blocklen = ikev2_encr_block_size(sa->encr);
+
+	iv = (CK_BYTE_PTR)buf + sizeof (ike_payload_t);
+	data = iv + ivlen;
+
+	/* Sanity check */
+	VERIFY3P(data, >=, (CK_BYTE_PTR)pkt->pkt_ptr);
+	datalen = (CK_ULONG)((CK_BYTE_PTR)pkt->pkt_ptr - data);
+
+	/*
+	 * Per RFC7296 3.14, the sender can choose any value for the padding.
+	 * We elect to use PKCS#7 style padding (repeat the pad value as the
+	 * padding).  This is well studied and appears to work.  Unfortunately,
+	 * we cannot validate the padding in the general case.  However,
+	 * since we know when we're communicating to other instances of
+	 * ourselves via the vendor ID payload, it is permissible to have
+	 * custom behavior in such instances, as long as we are backwards
+	 * compatible.  As such we DO validate the padding when communicating
+	 * to other instances of ourselves. Based on attacks to
+	 * protocols (e.g. TLS) where validation of the padding wasn't done,
+	 * we think this is prudent to do.
+	 */
+	if ((datalen + 1) % blocklen != 0)
+		padlen = blocklen - ((datalen + 1) % blocklen);
+
+	if (pkt_write_left(pkt) < padlen + 1 + icvlen) {
+		bunyan_info(sa->i2sa_log, "not enough space for packet",
+		    BUNYAN_T_END);
+		return (B_FALSE);
+	}
+
+	/*
+	 * Once we've written the padding out, we need to write out how much
+	 * padding was added.  Since the amount of padding and the value of
+	 * the padding are the same, we can just use <= in the loop test to
+	 * cause one extra iteration of the loop to accomplish that.
+	 */
+	for (size_t i = 0; i <= padlen; i++) {
+		*pkt->pkt_ptr = padlen;
+		pkt->pkt_ptr++;
+	}
+		
+	datalen += padlen;
+	icv = data + datalen;
+	*icv = padlen;
+	icv++;
+
+	/*
+	 * XXX: So far, every encryption mode wants a unique IV per packet.
+	 * For CBC modes, it also needs to be unpredictable.  Other modes do
+	 * not appear to have that requirement.  Since the msgid should be
+	 * unique for a given key (i.e. the msgid never resets for a given
+	 * IKE SA, instead a new IKE SA, with a new key is created).  We
+	 * start with that, and then for CBC modes, use follow the suggestion
+	 * in NIST 800-38A, Appendix C and encrypt the msgid to create the IV.
+	 */
+	VERIFY3S(ivlen, >=, sizeof (uint32_t));
+	if (mode == MODE_CBC) {
+		if (!cbc_iv(pkt, iv))
+			return (B_FALSE);
+	} else {
+		(void) memcpy(iv, &pkt->pkt_header.msgid, sizeof (uint32_t));
+	}
+
+	pkt->pkt_ptr = icv + icvlen;
+
+	/* Update SK payload length field to reflect padding + ICV */
+	ike_payload_t pay = { 0 };
+	(void) memcpy(&pay, buf, sizeof (pay));
+	pay.pay_length = htons((uint16_t)(pkt->pkt_ptr - buf));
+	(void) memcpy(buf, &pay, sizeof (pay));
+
+	if (!crypt_common(pkt, B_TRUE, iv, ivlen, data, datalen, icv, icvlen))
+		return (B_FALSE);
+
+	if (mode == MODE_CCM || mode == MODE_GCM)
+		return (B_TRUE);
+
+	if (!auth_common(pkt, B_TRUE, icv, icvlen))
+		return (B_FALSE);
+
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_pkt_decrypt(pkt_t *pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	CK_BYTE_PTR iv, data, icv;
+	CK_ULONG ivlen, datalen, icvlen;
+	uint8_t padlen = 0;
+	encr_modes_t mode = ikev2_encr_mode(sa->encr);
+
+	data = NULL;
+	datalen = 0;
+	for (size_t i = 0; i < pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pay = pkt_payload(pkt, i);
+
+		if (pay->pp_type != IKEV2_PAYLOAD_SK)
+			continue;
+
+		data = (CK_BYTE_PTR)pay->pp_ptr;
+		datalen = pay->pp_len;
+		break;
+	}
+	ASSERT3P(data, !=, NULL);
+
+	ivlen = ikev2_encr_iv_size(sa->encr);
+	icvlen = ikev2_auth_icv_size(sa->encr, sa->auth);
+	if (icvlen + icvlen + 1 >= datalen) {
+		bunyan_info(sa->i2sa_log, "SK payload is too small",
+		    BUNYAN_T_UINT32, "len", (uint32_t)datalen,
+		    BUNYAN_T_UINT32, "ivlen", (uint32_t)ivlen,
+		    BUNYAN_T_UINT32, "icvlen", (uint32_t)icvlen,
+		    BUNYAN_T_END);
+		return (B_FALSE);
+	}
+
+	iv = data;
+	data += ivlen;
+	datalen -= ivlen;
+	datalen -= icvlen;
+	icv = data + datalen;
+
+	if (mode != MODE_CCM && mode != MODE_GCM &&
+	    sa->encr != IKEV2_XF_AUTH_NONE) {
+		if (!auth_common(pkt, B_FALSE, icv, icvlen))
+			return (B_FALSE);
+	}
+
+	if (!crypt_common(pkt, B_FALSE, iv, ivlen, data, datalen, icv, icvlen))
+		return (B_FALSE);
+
+	padlen = *(icv - 1);
+	datalen -= padlen + 1;
+
+	/*
+	 * As described in enrypt_payloads(), when communicating with other
+	 * illumos instances, we opt to validate the contents of the padding.
+	 * Since RFC7296 allows the sender to choose any arbitrary value
+	 * for the padding, we cannot do this in the general case.
+	 */
+	if (pkt->pkt_sa->vendor == VENDOR_ILLUMOS_1) {
+		CK_BYTE_PTR padp = data + datalen;
+		for (size_t i = 0; i < padlen; i++) {
+			if (*padp == padlen)
+				continue;
+
+			bunyan_warn(sa->i2sa_log,
+			    "Padding validation failed",
+			    BUNYAN_T_UINT32, "padlen", (uint32_t)padlen,
+			    BUNYAN_T_UINT32, "offset", (uint32_t)i,
+			    BUNYAN_T_END);
+			return (B_FALSE);
+		}
+	}
+
+	ike_payload_t *payp, pay = { 0 };
+	size_t paycount = 0, ncount = 0;
+	size_t paystart, nstart;
+
+	payp = (ike_payload_t *)iv;
+	payp--;
+	(void) memcpy(&pay, payp, sizeof (ike_payload_t));
+
+	if (!pkt_count_payloads(data, datalen, pay.pay_next, &paycount,
+	    &ncount)) {
+		return (B_FALSE);
+	}
+
+	paystart = pkt->pkt_payload_count;
+	nstart = pkt->pkt_notify_count;
+	if (!pkt_size_index(pkt, pkt->pkt_payload_count + paycount,
+	    pkt->pkt_notify_count + ncount))
+		return (B_FALSE);
+
+	if (!pkt_index_payloads(pkt, data, datalen, pay.pay_next, paystart))
+		return (B_FALSE);
+
+	ncount = nstart;
+	for (size_t i = 0; i < pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pp = pkt_payload(pkt, i);
+
+		if (pp->pp_type != IKEV2_PAYLOAD_NOTIFY)
+			continue;
+
+		pkt_notify_t *np = NULL;
+		ikev2_notify_t n = { 0 };
+
+		np = pkt_notify(pkt, ncount++);
+		ASSERT3U(pp->pp_len, >=, sizeof (n));
+		(void) memcpy(&n, pp->pp_ptr, sizeof (n));
+		np->pn_ptr = pp->pp_ptr;
+		np->pn_len = pp->pp_len;
+		np->pn_type = ntohs(n.n_type);
+	}
+ 
+	return (B_TRUE);
+}
+
+boolean_t
+ikev2_add_config(pkt_t *pkt, ikev2_cfg_type_t cfg_type)
+{
+	return (B_FALSE);
+	/* TODO */
+}
+
+boolean_t
+ikev2_add_config_attr(pkt_t *restrict pkt,
+    ikev2_cfg_attr_type_t cfg_attr_type, const void *restrict data)
+{
+	return (B_FALSE);
+	/* TODO */
+}
+
+char *
+ikev2_pkt_desc(pkt_t *pkt)
+{
+	char *s = NULL;
+	size_t len = 0;
+	uint16_t i;
+	uint16_t j;
+
+	for (i = j = 0; i < pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pay = pkt_payload(pkt, i);
+		const char *paystr =
+		    ikev2_pay_short_str((ikev2_pay_type_t)pay->pp_type);
+
+		len += strlen(paystr) + 1;
+		if (pay->pp_type == IKEV2_PAYLOAD_NOTIFY) {
+			pkt_notify_t *n = pkt_notify(pkt, j++);
+			const char *nstr =
+			    ikev2_notify_str((ikev2_notify_type_t)n->pn_type);
+
+			len += strlen(nstr) + 2;
+		}
+	}
+
+	s = calloc(1, len);
+	VERIFY3P(s, !=, len);
+
+	for (i = j = 0; i <pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pay = pkt_payload(pkt, i);
+		const char *paystr =
+		    ikev2_pay_short_str((ikev2_pay_type_t)pay->pp_type);
+
+		(void) strlcat(s, paystr, len);
+		if (pay->pp_type == IKEV2_PAYLOAD_NOTIFY) {
+			pkt_notify_t *n = pkt_notify(pkt, j++);
+			const char *nstr =
+			    ikev2_notify_str((ikev2_notify_type_t)n->pn_type);
+
+			(void) strlcat(s, "(", len);
+			(void) strlcat(s, nstr, len);
+			(void) strlcat(s, ")", len);
+		}
+	}
+
+	return (s);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_pkt.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,88 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _IKEV2_PKT_H
+#define	_IKEV2_PKT_H
+
+#include <sys/types.h>
+
+#include "ikev2.h"
+#include "pkt.h"
+#include "buf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct pkt;
+struct ikev2_sa;
+
+#define	INBOUND_LOCAL_SPI(hdr) \
+	(((hdr)->flags == IKEV2_FLAG_INITIATOR) ? \
+	    (hdr)->responder_spi : (hdr)->initiator_spi)
+
+#define	INBOUND_REMOTE_SPI(hdr) \
+	(((hdr)->flags == IKEV2_FLAG_INITIATOR) ? \
+	    (hdr)->initiator_spi : (hdr)->responder_spi)
+
+pkt_t *ikev2_pkt_new_inbound(uchar_t *, size_t);
+pkt_t *ikev2_pkt_new_initiator(struct ikev2_sa *, ikev2_exch_t);
+pkt_t *ikev2_pkt_new_response(const pkt_t *);
+void ikev2_pkt_free(pkt_t *);
+
+boolean_t ikev2_add_sa(pkt_t *);
+boolean_t ikev2_add_prop(pkt_t *, uint8_t, ikev2_spi_proto_t, uint64_t);
+boolean_t ikev2_add_xform(pkt_t *, ikev2_xf_type_t, int);
+boolean_t ikev2_add_xf_attr(pkt_t *, ikev2_xf_attr_type_t, uintptr_t);
+boolean_t ikev2_add_xf_encr(pkt_t *, ikev2_xf_encr_t, uint16_t, uint16_t);
+boolean_t ikev2_add_ke(pkt_t *restrict, uint_t, const uchar_t *restrict,
+    size_t);
+boolean_t ikev2_add_id_i(pkt_t *restrict, ikev2_id_type_t, ...);
+boolean_t ikev2_add_id_r(pkt_t *restrict, ikev2_id_type_t, ...);
+boolean_t ikev2_add_cert(pkt_t *restrict, ikev2_cert_t,
+    const uchar_t *restrict, size_t);
+boolean_t ikev2_add_certreq(pkt_t *restrict, ikev2_cert_t,
+    const uchar_t *restrict, size_t);
+boolean_t ikev2_add_auth(pkt_t *restrict, ikev2_auth_type_t,
+    const uchar_t *restrict, size_t);
+boolean_t ikev2_add_nonce(pkt_t *restrict, size_t);
+boolean_t ikev2_add_notify(pkt_t *restrict, ikev2_spi_proto_t, uint64_t,
+    ikev2_notify_type_t, const void *restrict, size_t);
+
+boolean_t ikev2_add_delete(pkt_t *, ikev2_spi_proto_t);
+boolean_t ikev2_add_delete_spi(pkt_t *, uint64_t);
+
+boolean_t ikev2_add_vendor(pkt_t *restrict, const void *restrict, size_t);
+
+boolean_t ikev2_add_ts_i(pkt_t *);
+boolean_t ikev2_add_ts_r(pkt_t *);
+boolean_t ikev2_add_ts(pkt_t *restrict, ikev2_ts_type_t, uint8_t /* proto */,
+    const sockaddr_u_t *restrict /* start */,
+    const sockaddr_u_t *restrict /* end */);
+
+boolean_t ikev2_add_sk(pkt_t *);
+
+boolean_t ikev2_add_config(pkt_t *restrict, ikev2_cfg_type_t);
+boolean_t ikev2_add_config_attr(pkt_t *restrict, ikev2_cfg_attr_type_t,
+    const void *restrict);
+
+boolean_t ikev2_pkt_decrypt(pkt_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKEV2_PKT_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_proto.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,417 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2012 Jason King.
+ * Copyright 2017 Joyent, Inc
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include "defs.h"
+#include "config.h"
+#include "ikev2_sa.h"
+#include "ikev2_pkt.h"
+#include "worker.h"
+#include "inbound.h"
+#include "timer.h"
+#include "fromto.h"
+#include "ikev2_proto.h"
+#include "config.h"
+
+#define	SPILOG(_level, _log, _msg, _src, _dest, _lspi, _rspi, ...)	\
+	NETLOG(_level, _log, _msg, _src, _dest,				\
+	BUNYAN_T_UINT64, "local_spi", (_lspi),				\
+	BUNYAN_T_UINT64, "remote_spi", (_rspi),				\
+	## __VA_ARGS__)
+
+#define	PKT_PTR(pkt)	((uchar_t *)(pkt)->pkt_raw)
+
+static void ikev2_retransmit(te_event_t, void *);
+static int select_socket(const ikev2_sa_t *, const struct sockaddr_storage *);
+
+/*
+ * Find the IKEv2 SA for a given inbound packet (or create a new one if
+ * an IKE_SA_INIT exchange) and send packet to worker.
+ */
+void
+ikev2_dispatch(pkt_t *pkt, const struct sockaddr_storage *restrict l_addr,
+    const struct sockaddr_storage *restrict r_addr)
+{
+	ikev2_sa_t *i2sa = NULL;
+	uint64_t local_spi = INBOUND_LOCAL_SPI(&pkt->pkt_header);
+	uint64_t remote_spi = INBOUND_REMOTE_SPI(&pkt->pkt_header);
+
+	i2sa = ikev2_sa_get(local_spi, remote_spi, l_addr, r_addr, pkt);
+	if (i2sa != NULL)
+		goto dispatch;
+
+	if (local_spi != 0) {
+		/*
+		 * If the local SPI is set, we should be able to find it
+		 * in our hash.
+		 *
+		 * XXX: Should we respond with a notificaiton?
+		 *
+		 * RFC5996 2.21.4 we may send an INVALID_IKE_SPI
+		 * notification if we wish.
+		 *
+		 * For now, discard.
+		 */
+		SPILOG(debug, log, "cannot find existing IKE SA with "
+		    "matching local SPI value; discarding",
+		    r_addr, l_addr, local_spi, remote_spi);
+
+		ikev2_pkt_free(pkt);
+		return;
+	}
+
+	if (remote_spi == 0) {
+		/*
+		 * XXX: this might require special processing.
+		 * Discard for now.
+		 */
+		goto discard;
+	}
+
+	/*
+	 * If the local SPI == 0, this can only be an IKE SA INIT
+	 * exchange.  For all such exchanges, the msgid is always 0,
+	 * regardless of the the number of actual messages sent during
+	 * the exchange (RFC5996 2.2).
+	 */
+	if (pkt->pkt_header.exch_type != IKEV2_EXCH_IKE_SA_INIT ||
+	    pkt->pkt_header.msgid != 0) {
+		SPILOG(debug, log, "received non IKE_SA_INIT message with "
+		    "0 local spi; discarding", r_addr, l_addr, local_spi,
+		    remote_spi,
+		    BUNYAN_T_UINT32, "exch_type",
+		    (uint32_t)pkt->pkt_header.exch_type,
+		    BUNYAN_T_UINT32, "msgid", pkt->pkt_header.msgid);
+		goto discard;
+	}
+
+	/*
+	 * If there isn't an existing IKEv2 SA, the only
+	 * valid inbound packet is a request to start an
+	 * IKE_EXCH_IKE_SA_INIT exchange.
+	 */
+	if (!(pkt->pkt_header.flags & IKEV2_FLAG_INITIATOR)) {
+		SPILOG(debug, log, "cannot find existing SA", r_addr, l_addr,
+		    local_spi, remote_spi);
+		goto discard;
+	}
+
+	/* otherwise create a larval SA */
+	i2sa = ikev2_sa_alloc(B_FALSE, pkt, l_addr, r_addr);
+	if (i2sa == NULL) {
+		SPILOG(warn, log, "could not create IKEv2 SA", r_addr,
+		    l_addr, local_spi, remote_spi);
+		goto discard;
+	}
+	local_spi = I2SA_LOCAL_SPI(i2sa);
+
+dispatch:
+	pkt->pkt_sa = i2sa;
+	if (worker_dispatch(EVT_PACKET, pkt, local_spi % nworkers))
+		return;
+
+	SPILOG(info, log, "worker queue full; discarding packet",
+	    r_addr, l_addr, local_spi, remote_spi);
+
+discard:
+	if (i2sa != NULL)
+		I2SA_REFRELE(i2sa);
+	ikev2_pkt_free(pkt);
+}
+
+/*
+ * Sends a packet out.  If pkt is an error reply, is_error should be
+ * set so that it is not saved for possible retransmission.
+ *
+ */
+boolean_t
+ikev2_send(pkt_t *pkt, boolean_t is_error)
+{
+	ikev2_sa_t *i2sa = pkt->pkt_sa;
+	ssize_t len = 0;
+	int s = -1;
+	boolean_t initiator = !!(pkt->pkt_header.flags & IKEV2_FLAG_INITIATOR);
+
+	if (initiator) {
+		/*
+		 * We should not send out a new exchange while still waiting
+		 * on a response from a previous request
+		 */
+		ASSERT3P(sa->last_sent, ==, NULL);
+		pkt->pkt_header.msgid = i2sa->outmsgid;
+	}
+
+	if (!pkt_done(pkt)) {
+		I2SA_REFRELE(i2sa);
+		ikev2_pkt_free(pkt);
+		return (B_FALSE);
+	}
+
+	s = select_socket(i2sa, NULL);
+	len = sendfromto(s, PKT_PTR(pkt), pkt_len(pkt), &i2sa->laddr,
+	    &i2sa->raddr);
+	if (len == -1 && pkt != i2sa->init) {
+		/*
+		 * If it failed, should we still save it and let
+		 * ikev2_retransmit attempt?  For now, no.
+		 *
+		 * Note: sendfromto() should have logged any relevant errors
+		 */
+		I2SA_REFRELE(i2sa);
+		ikev2_pkt_free(pkt);
+		return (B_FALSE);
+	}
+
+	PTH(pthread_mutex_lock(&i2sa->lock));
+	if (initiator) {
+		/* XXX: bump & save for error messages? */
+
+		/*
+		 * In a few instances (e.g. cookies) we explicitly re-send the
+		 * last packet instead of waiting for the retransmit timeout
+		 * to kick in.  In those instances, we don't want to bump
+		 * the message since it's not a new exchange.
+		 */
+		if (pkt != i2sa->last_sent)
+			i2sa->outmsgid++;
+
+		i2sa->last_sent = pkt;
+
+		if (!is_error) {
+			config_t *cfg = config_get();
+			VERIFY(schedule_timeout(TE_TRANSMIT, ikev2_retransmit,
+			    pkt, cfg->cfg_retry_init));
+			CONFIG_REFRELE(cfg);
+		}
+	} else {
+		i2sa->last_resp_sent = pkt;
+	}
+	PTH(pthread_mutex_unlock(&i2sa->lock));
+
+	if (is_error) {
+		I2SA_REFRELE(i2sa);
+		ikev2_pkt_free(pkt);
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Retransmit callback
+ */
+static void
+ikev2_retransmit(te_event_t event, void *data)
+{
+	pkt_t *pkt = data;
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	hrtime_t retry = 0;
+	ssize_t len;
+
+	PTH(pthread_mutex_lock(&sa->lock));
+
+	/* XXX: what about condemned SAs */
+	if (sa->outmsgid > pkt->pkt_header.msgid || sa->last_sent == NULL) {
+		/* already acknowledged */
+		PTH(pthread_mutex_unlock(&sa->lock));
+		ikev2_pkt_free(pkt);
+		return;
+	}
+
+	config_t *cfg = config_get();
+	retry = cfg->cfg_retry_init * (1ULL << ++pkt->pkt_xmit);
+	if (retry > cfg->cfg_retry_max || pkt->pkt_xmit > cfg->cfg_retry_max) {
+		PTH(pthread_mutex_unlock(&sa->lock));
+		ikev2_sa_condemn(sa);
+		ikev2_pkt_free(pkt);
+		CONFIG_REFRELE(cfg);
+		return;
+	}
+	PTH(pthread_mutex_unlock(&sa->lock));
+	CONFIG_REFRELE(cfg);
+
+	len = sendfromto(select_socket(sa, NULL), PKT_PTR(pkt), pkt_len(pkt),
+	    &sa->laddr, &sa->raddr);
+	/* XXX: sendfromto() will log if it fails, do anything else? */
+
+	VERIFY(schedule_timeout(TE_TRANSMIT, ikev2_retransmit, pkt, retry));
+}
+
+/*
+ * Determine if the packet should be discarded, or retransmit our last
+ * packet if appropriate
+ * XXX better function name?
+ */
+static boolean_t
+ikev2_discard_pkt(pkt_t *pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	uint32_t msgid = pkt->pkt_header.msgid;
+	boolean_t discard = B_TRUE;
+
+	PTH(pthread_mutex_lock(&sa->lock));
+	if (sa->flags & I2SA_CONDEMNED)
+		goto done;
+
+	if (pkt->pkt_header.flags & IKEV2_FLAG_RESPONSE) {
+		pkt_t *last = sa->last_sent;
+
+		if (msgid != sa->outmsgid) {
+			/*
+			 * Not a response to our last message.
+			 * XXX: Send INVALID_MESSAGE_ID notification in
+			 * certain circumstances.  Drop for now.
+			 */
+			goto done;
+		}
+
+		/* A response to our last message */
+		VERIFY3S(cancel_timeout(TE_TRANSMIT, last,
+		    sa->i2sa_log), ==, 1);
+
+		/*
+		 * Corner case: this isn't the actual response in the
+		 * IKE_SA_INIT exchange, but a request to either use
+		 * cookies or a different DH group.  In that case we don't
+		 * want to treat it like a response (ending the exchange).
+		 */
+		if (pkt->pkt_header.exch_type != IKEV2_EXCH_IKE_SA_INIT ||
+		    pkt->pkt_header.responder_spi != 0)
+			sa->last_sent = NULL;
+
+		/* Keep the initial packet for duration of IKE SA */
+		if (last != sa->init)
+			ikev2_pkt_free(last);
+		discard = B_FALSE;
+		goto done;
+	}
+
+	ASSERT(pkt->pkt_header.flags & IKEV2_FLAG_INITIATOR);
+
+	if (msgid == sa->inmsgid) {
+		pkt_t *resp = sa->last_resp_sent;
+		ssize_t len = 0;
+
+		if (resp == NULL) {
+			discard = B_FALSE;
+			goto done;
+		}
+
+		len = sendfromto(select_socket(sa, NULL), PKT_PTR(resp),
+		    pkt_len(pkt), &sa->laddr, &sa->raddr);
+		goto done;
+	}
+
+	if (msgid != sa->inmsgid + 1) {
+		/*
+		 * XXX: create new informational exchange, send
+		 * INVALID_MESSAGE_ID notification?
+		 */
+		goto done;
+	}
+
+	/* new exchange, free last response and get going */
+	ikev2_pkt_free(sa->last_resp_sent);
+	sa->last_resp_sent = NULL;
+	sa->inmsgid++;
+	discard = B_FALSE;
+
+done:
+	PTH(pthread_mutex_unlock(&sa->lock));
+	return (discard);
+}
+
+/*
+ * Worker inbound function
+ */
+void
+ikev2_inbound(pkt_t *pkt)
+{
+	if (ikev2_discard_pkt(pkt))
+		return;
+
+	switch (pkt->pkt_header.exch_type) {
+	case IKEV2_EXCH_IKE_SA_INIT:
+		if (pkt->pkt_header.flags & IKEV2_FLAG_INITIATOR)
+			ikev2_sa_init_inbound_init(pkt);
+		else
+			ikev2_sa_init_inbound_resp(pkt);
+		break;
+	case IKEV2_EXCH_IKE_AUTH:
+	case IKEV2_EXCH_CREATE_CHILD_SA:
+	case IKEV2_EXCH_INFORMATIONAL:
+	case IKEV2_EXCH_IKE_SESSION_RESUME:
+		/* TODO */
+		bunyan_info(pkt->pkt_sa->i2sa_log,
+		    "Exchange not implemented yet", BUNYAN_T_END);
+		ikev2_pkt_free(pkt);
+		break;
+	default:
+		bunyan_error(pkt->pkt_sa->i2sa_log, "Unknown IKEV2 exchange",
+		    BUNYAN_T_UINT32, "exchange",
+		    (uint32_t)pkt->pkt_header.exch_type, BUNYAN_T_END);
+		ikev2_pkt_free(pkt);
+	}
+}
+
+static int
+select_socket(const ikev2_sa_t *i2sa, const struct sockaddr_storage *local)
+{
+	ASSERT((i2sa != NULL && local == NULL) ||
+	    (i2sa == NULL && local != NULL));
+
+	if (i2sa != NULL)
+		local = &i2sa->laddr;
+	if (local->ss_family == AF_INET6)
+		return (ikesock6);
+	if (i2sa != NULL && I2SA_IS_NAT(i2sa))
+		return (nattsock);
+	return (ikesock4);
+}
+
+void
+ikev2_no_proposal_chosen(ikev2_sa_t *restrict i2sa, const pkt_t *restrict src,
+    ikev2_spi_proto_t proto, uint64_t spi)
+{
+	pkt_t *resp = ikev2_pkt_new_response(src);
+
+	if (resp == NULL)
+		return;
+
+	if (!ikev2_add_notify(resp, proto, spi, IKEV2_N_NO_PROPOSAL_CHOSEN,
+	    NULL, 0)) {
+		ikev2_pkt_free(resp);
+		return;
+	}
+
+	/* Nothing can be done if send fails for this, so ignore return val */
+	(void) ikev2_send(resp, B_TRUE);
+
+	/* ikev2_send consumes packet, no need to free afterwards */
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_proto.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,47 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _IKEV2_PROTO_H
+#define	_IKEV2_PROTO_H
+
+#include <inttypes.h>
+#include "ikev2.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+struct pkt;
+struct ikev2_sa;
+struct sockaddr_storage;
+
+void ikev2_dispatch(struct pkt *, const struct sockaddr_storage *,
+    const struct sockaddr_storage *);
+boolean_t ikev2_send(struct pkt *, boolean_t);
+void ikev2_inbound(struct pkt *);
+
+void ikev2_sa_init_inbound_init(struct pkt *);
+void ikev2_sa_init_inbound_resp(struct pkt *);
+void ikev2_sa_init_outbound(struct ikev2_sa *);
+
+void ikev2_no_proposal_chosen(struct ikev2_sa *, const struct pkt *,
+    ikev2_spi_proto_t, uint64_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _IKEV2_PROTO_H */
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_sa.c	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,943 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+/*
+ * Manipulation and storage of IKEv2 Security Associations (SAs).
+ */
+#include <umem.h>
+#include <pthread.h>
+#include <errno.h>
+#include <strings.h>
+#include <locale.h>
+#include <stddef.h>
+#include <sys/types.h>
+#include <sys/sysmacros.h>
+#include <sys/debug.h>
+#include <limits.h>
+#include <note.h>
+#include <ipsec_util.h>
+#include <libuutil.h>
+#include "defs.h"
+#include "buf.h"
+#include "timer.h"
+#include "pkcs11.h"
+#include "ikev2_pkt.h"
+#include "ikev2_sa.h"
+#include "random.h"
+#include "worker.h"
+#include "config.h"
+
+/* Our hashes */
+enum {
+	LSPI,
+	RHASH
+};
+
+struct i2sa_bucket {
+	pthread_mutex_t		lock;	/* bucket lock */
+	uu_list_t		*chain;	/* hash chain of ikev2_sa_t's */
+};
+
+typedef struct i2sa_cmp_arg {
+	sockaddr_u_t		laddr;
+	sockaddr_u_t		raddr;
+	const pkt_t		*init_pkt;
+	uint64_t		lspi;
+	uint64_t		rspi;
+	int			hash;
+} i2sa_cmp_arg_t;
+
+static const struct {
+	const char	*name;
+	offset_t	offset;
+} pool_names[] = {
+	{ "ikev2_lspi_chain", offsetof(ikev2_sa_t, lspi_node) },
+	{ "ikev2_rhash_chain", offsetof(ikev2_sa_t, rhash_node) }
+};
+
+static volatile uint_t	half_open;	/* # of larval/half open IKEv2 SAs */
+static uint_t		num_buckets;	/* Use same value for all hashes */
+static uint32_t		remote_noise;	/* random noise for rhash */
+static i2sa_bucket_t	*hash[I2SA_NUM_HASH];
+static uu_list_pool_t	*list_pool[I2SA_NUM_HASH];
+static umem_cache_t	*i2sa_cache;
+
+#define	I2SA_KEY_LADDR		"laddr"
+#define	I2SA_KEY_LPORT		"lport"
+#define	I2SA_KEY_RADDR		"raddr"
+#define	I2SA_KEY_RPORT		"rport"
+#define	I2SA_KEY_LSPI		"lspi"
+#define	I2SA_KEY_RSPI		"rspi"
+#define	I2SA_KEY_INITIATOR	"initiator"
+
+#define	IKEV2_SA_HASH_SPI(spi) \
+    P2PHASE_TYPED((spi), num_buckets, uint64_t)
+
+#define	IKEV2_SA_RHASH(ss, spi) \
+    P2PHASE_TYPED(i2sa_rhash((ss), (spi)), num_buckets, uint64_t)
+
+static void	i2sa_init(ikev2_sa_t *);
+static uint32_t	i2sa_rhash(const struct sockaddr_storage *, uint64_t);
+
+static ikev2_sa_t *i2sa_verify(ikev2_sa_t *restrict, uint64_t,
+    const struct sockaddr_storage *restrict,
+    const struct sockaddr_storage *restrict);
+static boolean_t i2sa_add_to_hash(int, ikev2_sa_t *);
+
+static void i2sa_unlink(ikev2_sa_t *);
+static void i2sa_expire_cb(te_event_t, void *data);
+
+static boolean_t i2sa_key_add_addr(ikev2_sa_t *, const char *, const char *,
+    const struct sockaddr_storage *);
+static int i2sa_ctor(void *, void *, int);
+static void i2sa_dtor(void *, void *);
+
+static void inc_half_open(void);
+static void dec_half_open(void);
+
+/*
+ * Attempt to find an IKEv2 SA that matches the given criteria, or return
+ * NULL if not found.
+ */
+ikev2_sa_t *
+ikev2_sa_get(uint64_t l_spi, uint64_t r_spi,
+    const struct sockaddr_storage *restrict l_addr,
+    const struct sockaddr_storage *restrict r_addr,
+    const pkt_t *restrict init_pkt)
+{
+	i2sa_bucket_t *bucket;
+	ikev2_sa_t *sa;
+	i2sa_cmp_arg_t arg = { 0 };
+
+	arg.lspi = l_spi;
+	arg.rspi = r_spi;
+	arg.init_pkt = init_pkt;
+	arg.laddr.sau_ss = (struct sockaddr_storage *)l_addr;
+	arg.raddr.sau_ss = (struct sockaddr_storage *)r_addr;
+
+	if (l_spi != 0) {
+		/*
+		 * We assign the local SPIs, so if there is one, we should
+		 * only need that to find it.
+		 */
+		bucket = hash[LSPI] + IKEV2_SA_HASH_SPI(l_spi);
+		arg.hash = LSPI;
+	} else {
+		/* Otherwise gotta use the other stuff */
+		bucket = hash[RHASH] + IKEV2_SA_RHASH(r_addr, r_spi);
+		arg.hash = RHASH;
+	}
+
+	PTH(pthread_mutex_lock(&bucket->lock));
+	sa = (ikev2_sa_t *)uu_list_find(bucket->chain, NULL, &arg, NULL);
+	if (sa != NULL)
+		I2SA_REFHOLD(sa);
+	PTH(pthread_mutex_unlock(&bucket->lock));
+
+	return (i2sa_verify(sa, r_spi, l_addr, r_addr));
+}
+
+/*
+ * Allocate a larval IKEv2 SA.
+ *
+ * Obtains a unique local SPI and assigns it to the SA and adds the SA to
+ * the local SPI hash.  If the packet used to trigger the creation of the SA
+ * is given, take over management of it.  Also create an SA expiration timer.
+ *
+ * If we initiated the SA creation, the remote SPI will not be known initially.
+ * Once the protocol has proceeded enough to determine the remote SPI,
+ * ikev2_sa_set_rspi() should be called.
+ *
+ * Parameters:
+ * 	initiator	Was this SA locally initiated
+ * 	init_pkt	The packet that trigged the creation of the SA.
+ * 	laddr,
+ * 	raddr		The local and remote addresses of this SA.
+ *
+ * On successful create, the larval IKEv2 SA is returned.
+ * On failure, NULL is returned.  Caller maintains responsibility for
+ * init_pkt in this instance.
+ */
+ikev2_sa_t *
+ikev2_sa_alloc(boolean_t initiator,
+    pkt_t *restrict init_pkt,
+    const struct sockaddr_storage *restrict laddr,
+    const struct sockaddr_storage *restrict raddr)
+{
+	ikev2_sa_t	*i2sa = NULL;
+
+	if ((i2sa = umem_cache_alloc(i2sa_cache, UMEM_DEFAULT)) == NULL)
+		return (NULL);
+
+	if ((bunyan_child(log, &i2sa->i2sa_log) != 0)) {
+		umem_cache_free(i2sa_cache, i2sa);
+		return (NULL);
+	}
+
+	/* Keep anyone else out while we initialize */
+	PTH(pthread_mutex_lock(&i2sa->lock));
+
+	ASSERT((init_pkt == NULL) ||
+	    (init_pkt->hdr.exch_type == IKEV2_EXCHANGE_IKE_SA_INIT));
+
+	i2sa->flags |= (initiator) ? I2SA_INITIATOR : 0;
+
+	(void) memcpy(&i2sa->laddr, laddr, sizeof (i2sa->laddr));
+	(void) memcpy(&i2sa->raddr, raddr, sizeof (i2sa->raddr));
+
+	/* Get a random number for the local SPI that's currently unusued */
+	while (1) {
+		/*CONSTCOND*/
+		uint64_t spi;
+
+		/* 0 is never valid, exteremely unlikely, but easy to handle */
+		if ((spi = random_low_64()) == 0)
+			continue;
+
+		if (initiator)
+			i2sa->i_spi = spi;
+		else
+			i2sa->r_spi = spi;
+
+		if (i2sa_add_to_hash(LSPI, i2sa)) {
+			ASSERT(i2sa->refcnt == 1);
+
+			/* XXX: refhold i2sa in init_pkt */
+			i2sa->init = init_pkt;
+
+			/* refhold for caller */
+			I2SA_REFHOLD(i2sa);
+			break;
+		}
+	};
+
+	inc_half_open();
+
+	if (!i2sa_key_add_addr(i2sa, I2SA_KEY_LADDR, I2SA_KEY_LPORT, laddr) ||
+	    !i2sa_key_add_addr(i2sa, I2SA_KEY_RADDR, I2SA_KEY_RPORT, raddr) ||
+	    bunyan_key_add(i2sa->i2sa_log,
+	    BUNYAN_T_UINT64, I2SA_KEY_LSPI, I2SA_LOCAL_SPI(i2sa),
+	    BUNYAN_T_UINT64, I2SA_KEY_RSPI, I2SA_REMOTE_SPI(i2sa),
+	    BUNYAN_T_BOOLEAN, I2SA_KEY_INITIATOR,
+	    (i2sa->flags & I2SA_INITIATOR) ? B_TRUE : B_FALSE,
+	    BUNYAN_T_END) != 0)
+		goto fail;
+
+	/*
+	 * Start SA expiration timer.
+	 * XXX: Should this be reset after we've successfully authenticated?
+	 */
+
+	I2SA_REFHOLD(i2sa);	/* for the timer */
+	if (!schedule_timeout(TE_SA_EXPIRE, i2sa_expire_cb, i2sa,
+	    /* XXX: fixme */ 999 * NANOSEC)) {
+		bunyan_warn(i2sa->i2sa_log, "aborting larval IKEv2 SA creation",
+		    BUNYAN_T_END);
+
+		/* remove from hashes */
+		PTH(pthread_mutex_lock(&i2sa->lock));
+		i2sa_unlink(i2sa);
+		PTH(pthread_mutex_unlock(&i2sa->lock));
+
+		/* should be free'd once these references are released */
+		ASSERT(i2sa->refcnt == 2);
+		I2SA_REFRELE(i2sa); /* timer */
+		goto fail;
+	}
+
+	return (i2sa);
+
+fail:
+	ASSERT(i2sa->refcnt == 1);
+	I2SA_REFRELE(i2sa);
+	return (NULL);
+}
+
+/*
+ * Invoked when an SA has expired.  REF from timer is passed to this
+ * function.
+ */
+static void
+i2sa_expire_cb(te_event_t evt, void *data)
+{
+	NOTE(ARGUNUSED(evt))
+
+	ikev2_sa_t *i2sa = (ikev2_sa_t *)data;
+
+	/* XXX: todo */
+	I2SA_REFRELE(i2sa);
+}
+
+void
+ikev2_sa_flush(void)
+{
+	/* TODO: implement me */
+}
+
+void
+ikev2_sa_condemn(ikev2_sa_t *i2sa)
+{
+}
+
+void
+ikev2_sa_free(ikev2_sa_t *i2sa)
+{
+	if (i2sa == NULL)
+		return;
+
+	ASSERT(i2sa->refcnt == 0);
+
+	if (i2sa->i2sa_rule != NULL)
+		CONFIG_REFRELE(i2sa->i2sa_rule->rule_config);
+
+	/* All unauthenticated IKEv2 SAs are considered larval */
+	if (!(i2sa->flags & I2SA_AUTHENTICATED))
+		dec_half_open();
+
+	/*
+	 * XXX: we have potential circular references here
+	 * as ikev2_pkt_t->sa and i2sa->init,
+	 * i2sa->last_{resp_sent,sent,recvd} reference each other.
+	 *
+	 * We will need to sit and think about the lifecycles of
+	 * these packets to make sure when we want this SA to go
+	 * away for any reason, everything is properly cleaned up.
+	 *
+	 * For now, my thought is to punt until after the
+	 * IKE_SA_INIT and IKE_AUTH exchanges are written, as that
+	 * will likely help identify the best approach to resolving this.
+	 */
+	pkt_free(i2sa->init);
+	pkt_free(i2sa->last_resp_sent);
+	pkt_free(i2sa->last_sent);
+	pkt_free(i2sa->last_recvd);
+
+#define	DESTROY(x, y) pkcs11_destroy_obj(#y, &(x)->y, i2sa->i2sa_log)
+	DESTROY(i2sa, dh_pubkey);
+	DESTROY(i2sa, dh_privkey);
+	DESTROY(i2sa, dh_key);
+	DESTROY(i2sa, sk_d);
+	DESTROY(i2sa, sk_ai);
+	DESTROY(i2sa, sk_ar);
+	DESTROY(i2sa, sk_ei);
+	DESTROY(i2sa, sk_er);
+	DESTROY(i2sa, sk_pi);
+	DESTROY(i2sa, sk_pr);
+#undef  DESTROY
+
+	/* TODO: free child SAs */
+
+	bunyan_fini(i2sa->i2sa_log);
+
+	i2sa_dtor(i2sa, NULL);
+	i2sa_ctor(i2sa, NULL, 0);
+	umem_cache_free(i2sa_cache, i2sa);
+}
+
+void
+ikev2_sa_set_hashsize(uint_t numbuckets)
+{
+	i2sa_bucket_t *old[I2SA_NUM_HASH];
+	int i, hashtbl;
+	boolean_t startup;
+
+	for (i = 0; i < I2SA_NUM_HASH; i++)
+		old[i] = hash[i];
+
+	if (old[LSPI] == NULL)
+		startup = B_TRUE;
+	else
+		startup = B_FALSE;
+
+	if (!startup)
+		worker_suspend();
+
+	/* round up to a power of two if not already */
+	if (!ISP2(numbuckets)) {
+		ASSERT(sizeof (numbuckets) == 4);
+		--numbuckets;
+		for (i = 1; i <= 16; i++)
+			numbuckets |= (numbuckets >> i);
+		++numbuckets;
+	}
+	VERIFY(ISP2(numbuckets));
+
+	for (i = 0; i < I2SA_NUM_HASH; i++)
+		hash[i] = NULL;
+
+	/* Allocate new buckets */
+	for (i = 0; i < I2SA_NUM_HASH; i++) {
+		hash[i] = calloc(numbuckets, sizeof (i2sa_bucket_t));
+		if (hash[i] == NULL)
+			goto nomem;
+	}
+
+	uint32_t flags = UU_LIST_SORTED;
+
+#ifdef DEBUG
+	flags |= UU_LIST_DEBUG;
+#endif
+
+	for (hashtbl = 0; hashtbl < I2SA_NUM_HASH; hashtbl++) {
+		for (i = 0; i < numbuckets; i++) {
+			hash[hashtbl][i].chain =
+			    uu_list_create(list_pool[hashtbl], NULL, flags);
+			if (hash[hashtbl][i].chain == NULL)
+				goto nomem;
+
+			PTH(pthread_mutex_init(&hash[hashtbl][i].lock, NULL));
+		}
+	}
+
+	/* New tables means a new fudge factor.  Pick one randomly. */
+	remote_noise = random_low_32();
+
+	i = num_buckets;
+
+	/* Set this so the hash functions work on the new buckets */
+	num_buckets = numbuckets;
+
+	if (startup)
+		return;
+
+	/*
+	 * At this point, we've allocated all the necessary structures, so
+	 * we can just move everything over to the new buckets.  Since the
+	 * only remaining reference to the old number of buckets here is i,
+	 * we work backwards to free each chain, and invert the normal
+	 * inner/outer loop order.
+	 */
+	while (--i >= 0) {
+		for (hashtbl = 0; hashtbl < I2SA_NUM_HASH; hashtbl++) {
+			uu_list_t *list;
+			ikev2_sa_t *i2sa;
+			void *cookie;
+
+			cookie = NULL;
+			list = hash[hashtbl][i].chain;
+			for (i2sa = uu_list_teardown(list, &cookie);
+			    i2sa != NULL;
+			    i2sa = uu_list_teardown(list, &cookie)) {
+				/*
+				 * i2sa_add_to_hash() will create a new
+				 * ref, so we cannot transfer the ref from
+				 * the old hash into the new one, but must
+				 * release it instead.
+				 */
+				VERIFY(i2sa_add_to_hash(hashtbl, i2sa));
+				I2SA_REFRELE(i2sa);
+			}
+
+			PTH(pthread_mutex_destroy(&old[hashtbl][i].lock));
+			uu_list_destroy(old[hashtbl][i].chain);
+		}
+	}
+
+	for (hashtbl = 0; hashtbl < I2SA_NUM_HASH; hashtbl++)
+		free(old[hashtbl]);
+
+	worker_resume();
+	return;
+
+nomem:
+	if (startup)
+		errx(EXIT_FAILURE, "out of memory");
+
+	/* This will probably fail too, but worth a shot */
+	(void) bunyan_error(log, "out of memory", BUNYAN_T_STRING);
+
+	/*
+	 * Free what the new stuff we've constructed so far, and put the
+	 * old buckets back into place
+	 */
+	for (hashtbl = 0; hashtbl < I2SA_NUM_HASH; hashtbl++) {
+		if (hash[hashtbl] == NULL)
+			continue;
+		for (i = 0; i < numbuckets; i++) {
+			if (hash[hashtbl][i].chain != NULL)
+				uu_list_destroy(hash[hashtbl][i].chain);
+		}
+		free(hash[hashtbl]);
+		hash[hashtbl] = old[hashtbl];
+	}
+
+	worker_resume();
+}
+
+/*
+ * Set the remote SPI of an IKEv2 SA and add to the rhash
+ */
+void
+ikev2_sa_set_rspi(ikev2_sa_t *i2sa, uint64_t r_spi)
+{
+	/* better not be set already */
+	ASSERT(i2sa->r_spi == 0);
+
+	/* never a valid SPI value */
+	ASSERT(r_spi != 0);
+
+	/*
+	 * A bit confusing at times, but if we are the initiator of the
+	 * SA, the responder (ikev2_sa_t->r_spi) is the remote spi,
+	 * otherwise we are the responder, so the remote spi is the
+	 * initiator (ikev2_sa_t->i_spi)
+	 */
+	if (i2sa->flags & I2SA_INITIATOR)
+		i2sa->r_spi = r_spi;
+	else
+		i2sa->i_spi = r_spi;
+
+	VERIFY(i2sa_add_to_hash(RHASH, i2sa));
+}
+
+static i2sa_bucket_t *
+i2sa_get_bucket(int hashtbl, ikev2_sa_t *i2sa)
+{
+	i2sa_bucket_t *bucket;
+
+	VERIFY3S(hashtbl, <, I2SA_NUM_HASH);
+
+	bucket = hash[hashtbl];
+	switch (hashtbl) {
+	case LSPI:
+		bucket += IKEV2_SA_HASH_SPI(I2SA_LOCAL_SPI(i2sa));
+		break;
+	case RHASH:
+		bucket += IKEV2_SA_RHASH(&i2sa->raddr, I2SA_REMOTE_SPI(i2sa));
+		break;
+	}
+	return (bucket);
+}
+
+
+/*
+ * Add an IKEv2 SA to the given hash.
+ *
+ * Returns:
+ * 	B_TRUE	successfully added, hash holds ref to IKEv2 SA
+ * 	B_FALSE	IKEv2 SA already exists in hash, no ref held.
+ *
+ */
+static boolean_t
+i2sa_add_to_hash(int hashtbl, ikev2_sa_t *i2sa)
+{
+	i2sa_bucket_t	*bucket;
+	void		*node;
+	i2sa_cmp_arg_t	arg = { 0 };
+	uu_list_index_t idx;
+
+	VERIFY3S(hashtbl, <, I2SA_NUM_HASH);
+
+	bucket = i2sa_get_bucket(hashtbl, i2sa);
+	PTH(pthread_mutex_lock(&bucket->lock));
+
+	arg.hash = hashtbl;
+
+	/* Set idx to where the SA should be inserted */
+	node = uu_list_find(bucket->chain, i2sa, &arg, &idx);
+	if (node != NULL) {
+		/*
+		 * Found a match, should only happen while choosing
+		 * a local SPI value and we happen to pick one already
+		 * in use.
+		 */
+
+		VERIFY(node != i2sa);
+
+		/*
+		 * XXX: Should we do anything different for an rhash
+		 * match?
+		 */
+		PTH(pthread_mutex_unlock(&bucket->lock));
+		return (B_FALSE);
+	}
+
+	I2SA_REFHOLD(i2sa);	/* ref for chain */
+	i2sa->bucket[hashtbl] = bucket;
+	uu_list_insert(bucket->chain, i2sa, idx);
+	PTH(pthread_mutex_unlock(&bucket->lock));
+
+	return (B_TRUE);
+}
+
+static ikev2_sa_t *
+i2sa_verify(ikev2_sa_t *restrict i2sa, uint64_t rem_spi,
+    const struct sockaddr_storage *laddr,
+    const struct sockaddr_storage *raddr)
+{
+	if (i2sa == NULL)
+		return (NULL);
+
+	if (rem_spi != 0 && I2SA_REMOTE_SPI(i2sa) != rem_spi) {
+		/* XXX: log message */
+		goto bad_match;
+	}
+
+	if (laddr != NULL && !SA_ADDR_EQ(laddr, &i2sa->laddr)) {
+		/* XXX: log message */
+		goto bad_match;
+	}
+
+	if (raddr != NULL && !SA_ADDR_EQ(raddr, &i2sa->raddr)) {
+		/* XXX: log message */
+		goto bad_match;
+	}
+
+	/*
+	 * XXX KEBE ASKS - if remote port changes, do remap?
+	 * Probably have caller do this after packet is really legit.
+	 */
+
+	/* XXX KEBE SAYS FILL IN OTHER REALITY CHECKS HERE. */
+
+	/* XXX: log full match */
+	return (i2sa);
+
+bad_match:
+	I2SA_REFRELE(i2sa);
+	return (NULL);
+}
+
+static void
+i2sa_hash_remove(int hashtbl, ikev2_sa_t *i2sa)
+{
+	i2sa_bucket_t *bucket;
+
+	VERIFY3S(hashtbl, <, I2SA_NUM_HASH);
+	ASSERT(MUTEX_HELD(i2sa));
+
+	/* We shouldn't be holding the lock if this is the last reference */
+	ASSERT(i2sa->refcnt > 1);
+
+	bucket = i2sa_get_bucket(hashtbl, i2sa);
+	PTH(pthread_mutex_lock(&bucket->lock));
+	uu_list_remove(bucket->chain, i2sa);
+	i2sa->bucket[hashtbl] = NULL;
+	PTH(pthread_mutex_unlock(&bucket->lock));
+	I2SA_REFRELE(i2sa);
+}
+
+static void
+i2sa_unlink(ikev2_sa_t *i2sa)
+{
+	ASSERT(MUTEX_HELD(i2sa));
+	for (int i = 0; i < I2SA_NUM_HASH; i++)
+		i2sa_hash_remove(i, i2sa);
+}
+
+/*
+ * Generate a hash value for a remote SA based off the
+ * address and remote SPI.
+ */
+static uint32_t
+i2sa_rhash(const struct sockaddr_storage *ss, uint64_t spi)
+{
+	uint32_t rc = remote_noise;
+	const uint32_t *ptr = (const uint32_t *)&spi;
+	sockaddr_u_t ssu;
+
+	rc ^= ptr[0];
+	rc ^= ptr[1];
+
+	ssu.sau_ss = (struct sockaddr_storage *)ss;
+	if (ss->ss_family == AF_INET6) {
+		ptr = (const uint32_t *)&ssu.sau_sin6->sin6_addr;
+		rc ^= ptr[0];
+		rc ^= ptr[1];
+		rc ^= ptr[2];
+		rc ^= ptr[3];
+	} else {
+		ASSERT(ss->ss_family == AF_INET);
+		rc ^= ssu.sau_sin->sin_addr.s_addr;
+	}
+
+	return (rc);
+}
+
+/*
+ * Increase the count of larval SAs.  If we reach our threshold for larval SAs,
+ * enable the use of cookies.
+ */
+static void
+inc_half_open(void)
+{
+	atomic_inc_uint(&half_open);
+
+	/* TODO: cookie check */
+}
+
+/*
+ * Decrease the count of larval SAs.  Disable cookies if the count falls
+ * below the threshold
+ */
+static void
+dec_half_open(void)
+{
+	atomic_dec_uint(&half_open);
+
+	/*
+	 * TODO: Add cookie check.  Include hystersis to avoid potential
+	 * flopping.
+	 */
+}
+
+static int
+i2sa_ctor(void *buf, void *dummy, int flags)
+{
+	_NOTE(ARGUNUSUED(dummy, flags))
+
+	ikev2_sa_t *i2sa = (ikev2_sa_t *)&buf;
+
+	(void) memset(i2sa, 0, sizeof (*i2sa));
+
+	PTH(pthread_mutex_init(&i2sa->lock, NULL));
+	for (size_t i = 0; i < ARRAY_SIZE(pool_names); i++) {
+		uu_list_node_init(buf,
+		    (uu_list_node_t *)((uchar_t *)buf + pool_names[i].offset),
+		    list_pool[i]);
+	}
+
+	i2sa->msgwin = 1;
+	return (0);
+}
+
+static void
+i2sa_dtor(void *buf, void *dummy)
+{
+	_NOTE(ARGUNUSUED(dummy))
+
+	ikev2_sa_t *i2sa = (ikev2_sa_t *)buf;
+
+	PTH(pthread_mutex_destroy(&i2sa->lock));
+	for (size_t i = 0; i < I2SA_NUM_HASH; i++) {
+		uu_list_node_fini(buf,
+		    (uu_list_node_t *)((uchar_t *)buf + pool_names[i].offset),
+		    list_pool[i]);
+	}
+}
+
+static int
+sockaddr_compare(const struct sockaddr_storage *restrict l,
+    const struct sockaddr_storage *restrict r)
+{
+	sockaddr_u_t lu;
+	sockaddr_u_t ru;
+	int cmp;
+
+	if (l->ss_family > r->ss_family)
+		return (1);
+	if (l->ss_family < r->ss_family)
+		return (-1);
+
+	lu.sau_ss = (struct sockaddr_storage *)l;
+	ru.sau_ss = (struct sockaddr_storage *)r;
+
+	if (l->ss_family == AF_INET) {
+		cmp = memcmp(&lu.sau_sin->sin_addr, &ru.sau_sin->sin_addr,
+		    sizeof (lu.sau_sin->sin_addr));
+		if (cmp > 0)
+			return (1);
+		if (cmp < 0)
+			return (-1);
+
+		if (lu.sau_sin->sin_port > ru.sau_sin->sin_port)
+			return (1);
+		if (lu.sau_sin->sin_port < ru.sau_sin->sin_port)
+			return (-1);
+		return (0);
+	}
+
+	ASSERT(l->ss_family == AF_INET6);
+
+	cmp = memcmp(&lu.sau_sin6->sin6_addr, &ru.sau_sin6->sin6_addr,
+	    sizeof (lu.sau_sin6->sin6_addr));
+	if (cmp > 0)
+		return (1);
+	if (cmp < 0)
+		return (-1);
+
+	if (lu.sau_sin6->sin6_port > ru.sau_sin6->sin6_port)
+		return (1);
+	if (lu.sau_sin6->sin6_port < ru.sau_sin6->sin6_port)
+		return (-1);
+	return (0);
+}
+
+/*
+ * This is called by uu_list_find() to find an IKEv2 SA.  Since our
+ * uu_lists are sorted, it is also used to find the insertion spot
+ * in a hash chain.  When finding an IKEv2 SA, rarg will be NULL and
+ * arg will have the information to compare.  When finding an insertion
+ * spot, rarg will be the IKEv2 SA to insert to determine its location.
+ */
+static int
+i2sa_compare(const void *larg, const void *rarg, void *arg)
+{
+	const ikev2_sa_t *l = (const ikev2_sa_t *)larg;
+	const ikev2_sa_t *r = (const ikev2_sa_t *)rarg;
+	i2sa_cmp_arg_t *carg = (i2sa_cmp_arg_t *)arg;
+	const uchar_t *lbuf, *rbuf;
+	uint64_t spi;
+	size_t lsz, rsz;
+	int cmp;
+
+	if (carg->hash == LSPI) {
+		ASSERT(MUTEX_HELD(&l->bucket[LSPI]->lock));
+		if (r != NULL) {
+			ASSERT(l->bucket[LSPI] == r->bucket[LSPI]);
+			spi = I2SA_LOCAL_SPI(r);
+		} else {
+			spi = carg->lspi;
+		}
+
+		ASSERT(spi != 0);
+
+		/*
+		 * Since we assign the local SPI, we enforce that
+		 * they are globally unique
+		 */
+		if (I2SA_LOCAL_SPI(l) > spi)
+			return (1);
+		if (I2SA_LOCAL_SPI(l) < spi)
+			return (-1);
+		return (0);
+	}
+
+	ASSERT(carg->hash == RHASH);
+	ASSERT(MUTEX_HELD(l->bucket_lock_rhash));
+
+	i2sa_cmp_arg_t cmpdata = { 0 };
+	if (r != NULL) {
+		ASSERT3U(l->bucket_lock_rhash, ==, r->bucket_lock_rhash);
+		cmpdata.hash = RHASH;
+		cmpdata.rspi = I2SA_REMOTE_SPI(r);
+		cmpdata.raddr.sau_ss = (struct sockaddr_storage *)&r->raddr;
+		cmpdata.laddr.sau_ss = (struct sockaddr_storage *)&r->laddr;
+		cmpdata.init_pkt = r->init;
+		carg = &cmpdata;
+	}
+
+	if (I2SA_REMOTE_SPI(l) > carg->rspi)
+		return (1);
+	if (I2SA_REMOTE_SPI(l) < carg->rspi)
+		return (-1);
+
+	/* more likely to be different, so check these first */
+	cmp = sockaddr_compare(&l->raddr, carg->raddr.sau_ss);
+	if (cmp > 0)
+		return (1);
+	if (cmp < 0)
+		return (-1);
+
+	/* a multihomed system might have different local addresses */
+	cmp = sockaddr_compare(&l->laddr, carg->laddr.sau_ss);
+	if (cmp > 0)
+		return (1);
+	if (cmp < 0)
+		return (-1);
+
+	/*
+	 * RFC5996 2.1 - We cannot merely rely on the remote SPI and
+	 * address as clients behind NATs might choose the same SPI by chance.
+	 * We must in addition look at the initial packet.  This is only
+	 * an issue for half-opened remotely initiated SAs, as this is the
+	 * only time the local SPI is not yet known.
+	 */
+	cmp = memcmp(l->init->pkt_raw, carg->init_pkt->pkt_raw,
+	    MIN(pkt_len(l->init), pkt_len(carg->init_pkt)));
+	if (cmp != 0)
+		return ((cmp < 0) ? -1 : 1);
+	if (pkt_len(l->init) < pkt_len(carg->init_pkt))
+		return (-1);
+	if (pkt_len(l->init) > pkt_len(carg->init_pkt))
+		return (1);
+
+	return (0);
+}
+
+static boolean_t
+i2sa_key_add_addr(ikev2_sa_t *i2sa, const char *addr_key, const char *port_key,
+    const struct sockaddr_storage *addr)
+{
+	sockaddr_u_t sau;
+	sau.sau_ss = (struct sockaddr_storage *)addr;
+	int rc = 0;
+
+	switch (addr->ss_family) {
+	case AF_INET:
+		rc = bunyan_key_add(i2sa->i2sa_log,
+		    BUNYAN_T_IP, addr_key, &sau.sau_sin->sin_addr,
+		    BUNYAN_T_UINT32, port_key, (uint32_t)sau.sau_sin->sin_port,
+		    BUNYAN_T_END);
+		break;
+	case AF_INET6:
+		rc = bunyan_key_add(i2sa->i2sa_log,
+		    BUNYAN_T_IP6, addr_key, &sau.sau_sin6->sin6_addr,
+		    BUNYAN_T_UINT32, port_key,
+		    (uint32_t)sau.sau_sin6->sin6_port,
+		    BUNYAN_T_END);
+		break;
+	default:
+		INVALID("addr->ss_family");
+	}
+
+	return ((rc == 0) ? B_TRUE : B_FALSE);
+}
+
+void
+ikev2_sa_init(void)
+{
+	uint32_t flag = UU_LIST_SORTED;
+
+	if ((i2sa_cache = umem_cache_create("IKEv2 SAs", sizeof (ikev2_sa_t),
+	    0, i2sa_ctor, i2sa_dtor, NULL, NULL, NULL, 0)) == NULL)
+		err(EXIT_FAILURE, "Unable to allocate IKEv2 SA cache");
+
+#ifdef DEBUG
+	flag |= UU_LIST_POOL_DEBUG;
+#endif
+
+	for (int i = 0; i < ARRAY_SIZE(pool_names); i++) {
+		list_pool[i] = uu_list_pool_create(pool_names[i].name,
+		    sizeof (ikev2_sa_t), pool_names[i].offset, i2sa_compare,
+		    flag);
+
+		if (list_pool[i] != NULL)
+			err(EXIT_FAILURE,
+			    "Unable to allocate IKEv2 SA hash chains");
+	}
+}
+
+void
+ikev2_sa_fini(void)
+{
+	umem_cache_destroy(i2sa_cache);
+	for (int i = 0; i < I2SA_NUM_HASH; i++)
+		uu_list_pool_destroy(list_pool[i]);
+}
--- /dev/null	Wed Aug 23 21:32:33 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_sa.h	Wed Aug 23 21:32:33 2017
@@ -0,0 +1,196 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2014 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _IKEV2_SA_H
+#define	_IKEV2_SA_H
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <assert.h>
+#include <synch.h>
+#include <stddef.h>
+#include <security/cryptoki.h>
+#include <atomic.h>
+#include <pthread.h>
+#include <libuutil.h>
+
+#include "ikev2.h"
+#include "defs.h"
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+struct ikev2_sa;
+struct ikev2_child_sa;
+struct i2sa_bucket;
+struct pkt;
+struct buf;
+
+#ifndef IKEV2_SA_T
+#define	IKEV2_SA_T
+typedef struct ikev2_sa ikev2_sa_t;
+typedef struct ikev2_child_sa ikev2_child_sa_t;
+typedef struct i2sa_bucket i2sa_bucket_t;
+#endif /* IKEV2_SA_T */
+
+struct config_rule_s;
+
+#define	I2SA_NUM_HASH	2	/* The number of IKEv2 SA hashes we have */
+
+#define	I2SA_SALT_LEN	(32)	/* Maximum size of salt */
+
+/*
+ * The IKEv2 SA.
+ *
+ * This is the central data structure to the IKEv2 daemon.  It is a
+ * reference-counted node, where the lookup key is either the local
+ * SPI/cookie, or a hash based on the remote address and remote SPI.  (See
+ * ikev2_pkt.h for the _SPI() macros.)  It should be allocated with a umem
+ * cache.  It contains a mutex to lock certain fields if need be.
+ *
+ * Because of the distinct sets of lookup keys, it requires two linkages.
+ */
+struct ikev2_sa {
+	/*
+	 * Fields that should not be zeroed out between trips before
+	 * returning to the umem_cache should go at the top of this struct.
+	 */
+	pthread_mutex_t lock;
+
+	uu_list_node_t	lspi_node;
+	uu_list_node_t	rhash_node;
+
+	bunyan_logger_t	*i2sa_log;
+
+			/* Link to the bucket we are in for each hash */
+	i2sa_bucket_t	*bucket[I2SA_NUM_HASH];
+
+	struct config_rule_s	*i2sa_rule;
+
+	uint64_t		i_spi;	  /* Initiator SPI. */
+	uint64_t		r_spi;	  /* Responder SPI. */
+	uint32_t		flags;
+	volatile uint32_t	refcnt;
+
+	struct sockaddr_storage laddr;  /* Local address & port. */
+	struct sockaddr_storage raddr;  /* Remote address & port. */
+
+	vendor_t		vendor;
+
+	/* Current number of outstanding messages prior to outmsgid. */
+	int		msgwin;
+
+	int		encr;		/* Encryption algorithm */
+	size_t		encr_key_len;	/* Key length (bytes) for encr */
+	int		auth;		/* Authentication algorithm */
+	int		prf;		/* PRF algorithm */
+	int		dhgrp;		/* Diffie-Hellman group. */
+
+	uint32_t outmsgid;		/* Next msgid for outbound packets. */
+	uint32_t inmsgid;		/* Next expected inbound msgid. */
+
+	struct pkt	*init;  	/* IKE_SA_INIT packet. */
+	struct pkt	*last_resp_sent;
+	struct pkt	*last_sent;
+	struct pkt	*last_recvd;
+
+	time_t		birth;		/* When was AUTH completed */
+	hrtime_t	softexpire;
+	hrtime_t	hardexpire;
+
+	ikev2_child_sa_t	*child_sas;
+
+	CK_OBJECT_HANDLE dh_pubkey;
+	CK_OBJECT_HANDLE dh_privkey;
+	CK_OBJECT_HANDLE dh_key;
+	CK_OBJECT_HANDLE sk_d;
+	CK_OBJECT_HANDLE sk_ai;
+	CK_OBJECT_HANDLE sk_ar;
+	CK_OBJECT_HANDLE sk_ei;
+	CK_OBJECT_HANDLE sk_er;
+	CK_OBJECT_HANDLE sk_pi;
+	CK_OBJECT_HANDLE sk_pr;
+
+	uint8_t		salt[I2SA_SALT_LEN];
+	size_t		saltlen;
+};
+
+struct ikev2_child_sa {
+	ikev2_child_sa_t	*next;
+	ikev2_spi_proto_t	satype;
+	uint32_t		spi;
+	/* XXX: more to come probably */
+};
+
+/* SA flags */
+#define	I2SA_INITIATOR		0x1	/* Am I the initiator of this IKE SA? */
+#define	I2SA_NAT_LOCAL		0x2	/* I am behind a NAT. */
+#define	I2SA_NAT_REMOTE		0x4	/* My peer is behind a NAT. */
+#define	I2SA_CONDEMNED		0x8	/* SA is unlinked from a tree. */
+#define	I2SA_AUTHENTICATED	0x10	/* SA has been authenticated */
+
+#define	I2SA_LOCAL_SPI(i2sa) \
+	(((i2sa)->flags & I2SA_INITIATOR) ? (i2sa)->i_spi : \
+	    (i2sa)->r_spi)
+
+#define	I2SA_REMOTE_SPI(i2sa) \
+	(((i2sa)->flags & I2SA_INITIATOR) ? (i2sa)->r_spi : \
+	    (i2sa)->i_spi)
+
+#define	I2SA_IS_NAT(i2sa) \
+	(!!((i2sa)->flags && (I2SA_NAT_LOCAL|I2SA_NAT_REMOTE)))
+
+#define	I2SA_REFHOLD(i2sa) \
+	atomic_inc_32(&(i2sa)->refcnt)
+
+/* Stupid C tricks stolen from <assert.h>. */
+#define	I2SA_REFRELE(i2sa) \
+	(void) ((atomic_dec_32_nv(&(i2sa)->refcnt) != 0) || \
+	    (ikev2_sa_free(i2sa), 0))
+
+extern ikev2_sa_t *ikev2_sa_get(uint64_t, uint64_t,
+    const struct sockaddr_storage *restrict,
+    const struct sockaddr_storage *restrict,
+    const struct pkt *restrict);
+extern ikev2_sa_t *ikev2_sa_alloc(boolean_t, struct pkt *restrict,
+    const struct sockaddr_storage *restrict,
+    const struct sockaddr_storage *restrict);
+
+extern void	ikev2_sa_free(ikev2_sa_t *);
+extern void	ikev2_sa_condemn(ikev2_sa_t *);
+
+extern void	ikev2_sa_flush(void);
+extern void	ikev2_sa_set_hashsize(uint_t);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* _IKEV2_SA_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/ikev2_sa_init.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,522 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright (c) 2017 Joyent, Inc.
+ */
+
+#include <pthread.h>
+#include <umem.h>
+#include <err.h>
+#include <sys/debug.h>
+#include <bunyan.h>
+#include <time.h>
+#include <string.h>
+#include "defs.h"
+#include "worker.h"
+#include "pkt.h"
+#include "timer.h"
+#include "pkcs11.h"
+#include "ikev2_proto.h"
+#include "ikev2_sa.h"
+#include "config.h"
+#include "ikev2_pkt.h"
+#include "ikev2_enum.h"
+
+static boolean_t find_config(pkt_t *, sockaddr_u_t, sockaddr_u_t);
+static boolean_t add_nat(pkt_t *);
+static boolean_t check_nats(pkt_t *);
+static void check_vendor(pkt_t *);
+static boolean_t add_vendor(pkt_t *);
+static boolean_t add_rule_proposals(pkt_t *restrict,
+    const config_rule_t *restrict, uint64_t);
+static boolean_t add_cookie(pkt_t *restrict, void *restrict, size_t len);
+
+/*
+ * New inbound IKE_SA_INIT exchange
+ */
+void
+ikev2_sa_init_inbound_init(pkt_t *pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	pkt_t *resp = NULL;
+	sockaddr_u_t laddr = { .sau_ss = &sa->laddr };
+	sockaddr_u_t raddr = { .sau_ss = &sa->raddr };
+	size_t noncelen = 0;
+
+	VERIFY(!(sa->flags & I2SA_INITIATOR));
+
+	if (!find_config(pkt, laddr, raddr))
+		goto fail;
+	if (!check_nats(pkt))
+		goto fail;
+	check_vendor(pkt);
+
+	sa->init = pkt;
+
+	resp = ikev2_pkt_new_response(pkt);
+	if (resp == NULL)
+		goto fail;
+
+	/* XXX: SA */
+	/* XXX: KE */
+
+	if (!ikev2_add_nonce(resp, noncelen))
+		goto fail;
+	if (!add_nat(resp))
+		goto fail;
+
+	/* XXX: other notifications */
+
+	if (!add_vendor(resp))
+		goto fail;
+
+	if (!ikev2_send(resp, B_FALSE))
+		goto fail;
+	return;
+
+fail:
+	ikev2_pkt_free(pkt);
+	ikev2_pkt_free(resp);
+	/* XXX: anything else */
+}
+
+void
+ikev2_sa_init_inbound_resp(pkt_t *pkt)
+{
+	pkt_notify_t *cookie = pkt_get_notify(pkt, (uint16_t)IKEV2_N_COOKIE,
+	    NULL);
+
+	if (cookie != NULL) {
+		pkt_t *out = pkt->pkt_sa->init;
+
+		if (!add_cookie(out, cookie->pn_ptr, cookie->pn_len) ||
+		    !ikev2_send(out, B_FALSE)) {
+			/* XXX: destroy IKE SA */
+		}
+		return;
+	}
+
+}
+
+void
+ikev2_sa_init_outbound(ikev2_sa_t *i2sa)
+{
+	pkt_t *pkt = NULL;
+	sockaddr_u_t laddr = { .sau_ss = &i2sa->laddr };
+	sockaddr_u_t raddr = { .sau_ss = &i2sa->raddr };
+	size_t noncelen = 0;
+
+	VERIFY(i2sa->flags & I2SA_INITIATOR);
+
+	pkt = ikev2_pkt_new_initiator(i2sa, IKEV2_EXCH_IKE_SA_INIT);
+
+	if (!find_config(pkt, laddr, raddr))
+		goto fail;
+
+	/* XXX: COOKIE */
+
+	if (!add_rule_proposals(pkt, i2sa->i2sa_rule, 0))
+		goto fail;
+
+	/* XXX: KE */
+
+	if (!ikev2_add_nonce(pkt, noncelen))
+		goto fail;
+	if (!add_nat(pkt))
+		goto fail;
+	if (!add_vendor(pkt))
+		goto fail;
+
+	/* XXX: CERTREQ */
+
+	i2sa->init = pkt;
+
+	if (!ikev2_send(pkt, B_FALSE))
+		goto fail;
+	return;
+
+fail:
+	ikev2_pkt_free(pkt);
+	/* XXX: destroy SA? */
+}
+
+/*
+ * XXX: These two functions should probably be moved at some point so they
+ * can be used both for initial IKE SA creation and for IKE re-keying
+ * (which operates as a type of CREATE_CHILD_SA exchange
+ */
+static boolean_t
+add_rule_xform(pkt_t *restrict pkt, const config_xf_t *restrict xf)
+{
+	encr_modes_t mode = ikev2_encr_mode(xf->xf_encr);
+	boolean_t ok = B_TRUE;
+
+	ok &= ikev2_add_xf_encr(pkt, xf->xf_encr, xf->xf_minbits,
+	    xf->xf_maxbits);
+
+	/*
+	 * For all currently known combined mode ciphers, we don't need
+	 * to also include an integrity transform
+	 */
+	if (!MODE_IS_COMBINED(mode))
+		ok &= ikev2_add_xform(pkt, IKEV2_XF_AUTH, xf->xf_auth);
+
+	ok &= ikev2_add_xform(pkt, IKEV2_XF_DH, xf->xf_dh);
+
+	/*
+	 * XXX: IKEV1 determined the PRF based on the authentication method.
+	 * IKEV2 allows it to be negotiated separately.  Eventually we
+	 * should probably add an option to specify it in a transform
+	 * definition.  For now, we just include all the ones we support
+	 * in decreasing order of preference.
+	 */
+	ikev2_prf_t supported[] = {
+	    IKEV2_PRF_HMAC_SHA2_512,
+	    IKEV2_PRF_HMAC_SHA2_384,
+	    IKEV2_PRF_HMAC_SHA2_256,
+	    IKEV2_PRF_HMAC_SHA1,
+	    IKEV2_PRF_HMAC_MD5
+	};
+
+	for (size_t i = 0; i < ARRAY_SIZE(supported); i++)
+		ok &= ikev2_add_xform(pkt, IKEV2_XF_PRF, supported[i]);
+
+	return (ok);
+}
+
+static boolean_t
+add_rule_proposals(pkt_t *restrict pkt, const config_rule_t *restrict rule,
+    uint64_t spi)
+{
+	boolean_t ok = B_TRUE;
+
+	if (!ikev2_add_sa(pkt))
+		return (B_FALSE);
+
+	for (uint8_t i = 0; rule->rule_xf[i] != NULL; i++) {
+		/* RFC7296 3.3.1 proposal numbers start with 1 */
+		ok &= ikev2_add_prop(pkt, i + 1, IKEV2_PROTO_IKE, spi);
+		ok &= add_rule_xform(pkt, rule->rule_xf[i]);
+	}
+
+	return (ok);
+}
+
+static boolean_t
+find_config(pkt_t *pkt, sockaddr_u_t laddr, sockaddr_u_t raddr)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+
+	sa->i2sa_rule = config_get_rule(&laddr, &raddr);
+
+	if (sa->i2sa_rule->rule_xf[0] == NULL) {
+		bunyan_debug(sa->i2sa_log, "No rules found", BUNYAN_T_END);
+		ikev2_no_proposal_chosen(sa, pkt, IKEV2_PROTO_IKE, 0);
+		return (B_FALSE);
+	}
+
+	if (RULE_IS_DEFAULT(sa->i2sa_rule)) {
+		bunyan_debug(sa->i2sa_log, "Using default rule", BUNYAN_T_END);
+	} else {
+		bunyan_debug(sa->i2sa_log, "Found rule",
+		    BUNYAN_T_STRING, "label", sa->i2sa_rule->rule_label,
+		    BUNYAN_T_END);
+	}
+	return (B_TRUE);
+}
+
+#define	NAT_LEN	(20)
+/*
+ * Check if the NAT {src,dest} payload matches our IP address.
+ * Return 0 if match (i.e. no nat)
+ *     1 if match
+ *     -1 on error
+ */
+static int
+check_one_nat(pkt_t *pkt, pkt_notify_t *n)
+{
+	bunyan_logger_t *l = pkt->pkt_sa->i2sa_log;
+	CK_SESSION_HANDLE h = p11h;
+	sockaddr_u_t addr;
+	CK_MECHANISM mech = { .mechanism = CKM_SHA_1 };
+	buf_t buf[3];
+	CK_BYTE data[NAT_LEN] = { 0 };
+	buf_t out = { .b_ptr = data, .b_len = sizeof (data) };
+	CK_LONG len = 0;
+	CK_RV rv;
+
+	switch (n->pn_type) {
+	case IKEV2_N_NAT_DETECTION_SOURCE_IP:
+		addr.sau_ss = &pkt->pkt_sa->laddr;
+		break;
+	case IKEV2_N_NAT_DETECTION_DESTINATION_IP:
+		addr.sau_ss = &pkt->pkt_sa->raddr;
+		break;
+	default:
+		INVALID(n->pn_type);
+		/*NOTREACHED*/
+		return (-1);
+	}
+
+	buf[0].b_ptr = (CK_BYTE_PTR)&pkt->pkt_raw;
+	buf[0].b_len = 2 * sizeof (uint64_t);
+
+	switch (addr.sau_ss->ss_family) {
+	case AF_INET:
+		buf[1].b_ptr = (CK_BYTE_PTR)&addr.sau_sin->sin_addr;
+		buf[1].b_len = sizeof (in_addr_t);
+		buf[2].b_ptr = (CK_BYTE_PTR)&addr.sau_sin->sin_port;
+		buf[2].b_len = sizeof (addr.sau_sin->sin_port);
+		break;
+	case AF_INET6:
+		buf[1].b_ptr = (CK_BYTE_PTR)&addr.sau_sin6->sin6_addr;
+		buf[1].b_len = sizeof (in6_addr_t);
+		buf[2].b_ptr = (CK_BYTE_PTR)&addr.sau_sin6->sin6_port;
+		buf[2].b_len = sizeof (addr.sau_sin6->sin6_port);
+		break;
+	default:
+		INVALID("addr.sau_ss->ss_family");
+		return (-1);
+	}
+
+	if (n->pn_len != NAT_LEN) {
+		bunyan_error(l, "Invalid notify payload size",
+		    BUNYAN_T_STRING, "notify_type",
+		    ikev2_notify_str(n->pn_type),
+		    BUNYAN_T_UINT32, "payload_size", (uint32_t)n->pn_len,
+		    BUNYAN_T_UINT32, "expected_size", (uint32_t)NAT_LEN,
+		    BUNYAN_T_END);
+		return (-1);
+	}
+
+	if (!pkcs11_digest(CKM_SHA_1, buf, ARRAY_SIZE(buf), &out, l))
+		return (-1);
+
+	VERIFY3U(n->pn_len, ==, sizeof (data));
+	if (memcmp(n->pn_ptr, data, sizeof (data)) == 0)
+		return (0);
+
+	return (1);
+}
+
+/*
+ * Perform NAT detection and update IKEV2 SA accordingly.  Return B_FALSE on
+ * error, B_TRUE if no error.
+ */
+static boolean_t
+check_nats(pkt_t *pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+	pkt_notify_t *n = NULL;
+	int rc = 0;
+	boolean_t local_nat = B_TRUE;
+	boolean_t remote_nat = B_TRUE;
+
+	/*
+	 * Since the SOURCE/DESTINATION designation is from the perspective
+	 * of the remote side, the local/remote notion is reversed on our
+	 * side.
+	 */
+	n = pkt_get_notify(pkt, IKEV2_N_NAT_DETECTION_SOURCE_IP, NULL);
+	if (n == NULL) {
+		/* If the notification isn't present, assume no NAT */
+		remote_nat = B_FALSE;
+	} else {
+		while (n != NULL) {
+			rc = check_one_nat(pkt, n);
+			if (rc == -1) {
+				return (B_FALSE);
+			} else if (rc == 0) {
+				remote_nat = B_FALSE;
+				break;
+			}
+			n = pkt_get_notify(pkt,
+			    IKEV2_N_NAT_DETECTION_SOURCE_IP, n);
+		}
+	}
+
+	if (remote_nat) {
+		bunyan_debug(sa->i2sa_log, "Remote NAT detected", BUNYAN_T_END);
+		sa->flags |= I2SA_NAT_REMOTE;
+	}
+
+	n = pkt_get_notify(pkt, IKEV2_N_NAT_DETECTION_DESTINATION_IP, NULL);
+	if (n == NULL) {
+		/* Similar as above */
+		local_nat = B_FALSE;
+	} else {
+		while (n != NULL) {
+			rc = check_one_nat(pkt, n);
+			if (rc == -1) {
+				return (B_FALSE);
+			} else if (rc == 0) {
+				local_nat = B_FALSE;
+				break;
+			}
+			n = pkt_get_notify(pkt,
+			    IKEV2_N_NAT_DETECTION_DESTINATION_IP, n);
+		}
+	}
+
+	if (local_nat) {
+		bunyan_debug(sa->i2sa_log, "Local NAT detected", BUNYAN_T_END);
+		sa->flags |= I2SA_NAT_LOCAL;
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * RFC7296 2.23 -- Add NAT detection notifiation payloads.  The notification
+ * payload consists of the SHA-1 has of the SPIs (in order as they appear in
+ * the header), IP address, and port.
+ */
+static boolean_t
+add_nat(pkt_t *pkt)
+{
+	bunyan_logger_t *l = pkt->pkt_sa->i2sa_log;
+	sockaddr_u_t addr[2];
+	uint64_t spi[2];
+	ikev2_notify_type_t ntype[2];
+	buf_t buf[3];
+
+	addr[0].sau_ss = &pkt->pkt_sa->laddr;
+	addr[1].sau_ss = &pkt->pkt_sa->raddr;
+	ntype[0] = IKEV2_N_NAT_DETECTION_SOURCE_IP;
+	ntype[1] = IKEV2_N_NAT_DETECTION_DESTINATION_IP;
+
+	/*
+	 * These normally don't get converted to network byte order until
+	 * the packet has finished construction, so we need to do local
+	 * conversion for the NAT payload creation
+	 */
+	spi[0] = htonll(pkt->pkt_header.initiator_spi);
+	spi[1] = htonll(pkt->pkt_header.responder_spi);
+
+	buf[0].b_ptr = (CK_BYTE_PTR)&spi;
+	buf[0].b_len = sizeof (spi);
+
+	for (int i = 0; i < 2; i++) {
+		uchar_t data[NAT_LEN] = { 0 };
+		buf_t out = { .b_ptr = data, .b_len = sizeof (data) };
+
+		switch (addr[i].sau_ss->ss_family) {
+		case AF_INET:
+			buf[1].b_ptr = (CK_BYTE_PTR)&addr[i].sau_sin->sin_addr;
+			buf[1].b_len = sizeof (in_addr_t);
+			buf[2].b_ptr = (CK_BYTE_PTR)&addr[i].sau_sin->sin_port;
+			buf[2].b_len = sizeof (addr[i].sau_sin->sin_port);
+			break;
+		case AF_INET6:
+			buf[1].b_ptr =
+			    (CK_BYTE_PTR)&addr[i].sau_sin6->sin6_addr;
+			buf[1].b_len = sizeof (in6_addr_t);
+			buf[2].b_ptr =
+			    (CK_BYTE_PTR)&addr[i].sau_sin6->sin6_port;
+			buf[2].b_len = sizeof (addr[i].sau_sin6->sin6_port);
+			break;
+		default:
+			INVALID("addr.sau_ss->ss_family");
+			return (B_FALSE);
+		}
+
+		if (!pkcs11_digest(CKM_SHA_1, buf, ARRAY_SIZE(buf), &out, l))
+			return (B_FALSE);
+
+		if (!ikev2_add_notify(pkt, IKEV2_PROTO_IKE, 0, ntype[i],
+		     data, sizeof (data)))
+			return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static void
+check_vendor(pkt_t *pkt)
+{
+	ikev2_sa_t *sa = pkt->pkt_sa;
+
+	for (uint16_t i = 0; i < pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pay = pkt_payload(pkt, i);
+
+		if (pay->pp_type != IKEV2_PAYLOAD_VENDOR)
+			continue;
+		if (pay->pp_len != sizeof (VENDOR_STR_ILLUMOS_1))
+			continue;
+
+		if (memcmp(VENDOR_STR_ILLUMOS_1, pay->pp_ptr,
+		    sizeof (VENDOR_STR_ILLUMOS_1)) == 0) {
+			bunyan_debug(sa->i2sa_log,
+			    "Found illumos_1 vendor payload", BUNYAN_T_END);
+			sa->vendor = VENDOR_ILLUMOS_1;
+		}
+	}
+}
+
+static boolean_t
+add_vendor(pkt_t *pkt)
+{
+	return (ikev2_add_vendor(pkt, (uchar_t *)VENDOR_STR_ILLUMOS_1,
+	    sizeof (VENDOR_STR_ILLUMOS_1)));
+}
+
+static boolean_t
+add_cookie(pkt_t *restrict pkt, void *restrict cookie, size_t len)
+{
+	pkt_notify_t *n = pkt_get_notify(pkt, IKEV2_N_COOKIE, NULL);
+	uint8_t *start = (uint8_t *)pkt_start(pkt) + sizeof (ike_header_t) +
+	    sizeof (ikev2_payload_t);
+	ssize_t total = sizeof (ikev2_payload_t) + sizeof (ikev2_notify_t) +
+	    len;
+	size_t num = 1;
+	ikev2_notify_t ntfy = {
+	    .n_protoid = IKEV2_PROTO_IKE,
+	    .n_spisize = 0,
+	    .n_type = htons((uint16_t)IKEV2_N_COOKIE)
+	};
+
+	/*
+	 * If there's no existing cookie payload, make room for one,
+	 * otherwise just shift existing payloads (if necessary) to reuse
+	 * existing cookie payload
+	 */
+	if (n == NULL) {
+		total += sizeof (ikev2_payload_t) + sizeof (ikev2_notify_t);
+		num = 1;
+	} else {
+		/* Per RFC7296 2.6, this better be the first payload */
+		VERIFY3P(start, ==, (uint8_t *)n->pn_ptr);
+		total += len - (ssize_t)n->pn_len;
+		num = 0;
+	}
+
+	if (total > 0 && pkt_write_left(pkt) < total)
+		return (B_FALSE);
+
+	if (!(pkt->pkt_header.flags & IKEV2_FLAG_INITIATOR)) {
+		/* Simple case, we are responding with own cookie */
+
+		/* Should be the only payload */
+		VERIFY3U(pkt->pkt_payload_count, ==, 0);
+
+		return (ikev2_add_notify(pkt, IKEV2_PROTO_IKE, 0,
+		    IKEV2_N_COOKIE, cookie, len));
+	}
+
+	/* Make room to insert new first payload */
+	if (!pkt_pay_shift(pkt, (uint8_t)IKEV2_PAYLOAD_NOTIFY, num, total))
+		return (B_FALSE);
+
+	(void) memcpy(start, &ntfy, sizeof (ntfy));
+	(void) memcpy(start + sizeof (ntfy), cookie, len);
+	return (B_TRUE);
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/inbound.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,221 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/udp.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <err.h>
+#include <errno.h>
+#include <pthread.h>
+#include <string.h>
+#include <port.h>
+#include <assert.h>
+#include <locale.h>
+#include <ipsec_util.h>
+#include <note.h>
+#include <err.h>
+#include <sys/debug.h>
+#include <bunyan.h>
+#include "inbound.h"
+#include "defs.h"
+#include "ikev2.h"
+#include "ikev2_pkt.h"
+#include "fromto.h"
+#include "ikev2_proto.h"
+
+static uchar_t *inbound_buf(void);
+static pthread_key_t inbound_key = PTHREAD_ONCE_KEY_NP;
+
+int ikesock4 = -1;
+int ikesock6 = -1;
+int nattsock = -1;
+
+static void
+inbound(int s, void *arg)
+{
+	_NOTE(ARGUNUSED(arg))
+
+	uchar_t *buf = NULL;
+	pkt_t *pkt = NULL;
+	struct sockaddr_storage to = { 0 };
+	struct sockaddr_storage from = { 0 };
+	socklen_t tolen = sizeof (to);
+	socklen_t fromlen = sizeof (from);
+	ssize_t pktlen;
+
+	buf = inbound_buf();
+	(void) memset(buf, 0, MAX_PACKET_SIZE);
+
+	pktlen = recvfromto(s, buf, MAX_PACKET_SIZE, 0, &from, &fromlen, &to,
+	    &tolen);
+	schedule_socket(s, inbound);
+
+	if (pktlen == -1) {
+		/* recvfromto() should have dumped enough debug info */
+		return;
+	}
+
+	/*
+	 * recvfromto() guarantees we've received at least ike_header_t
+	 * bytes (or it returns -1)
+	 */
+
+	/* sanity checks */
+	ike_header_t *hdr = (ike_header_t *)buf;
+	size_t hdrlen = ntohl(hdr->length);
+
+	if (hdrlen != pktlen) {
+		NETLOG(info, log, "ISAKMP header length doesn't match "
+		    "received length; discarding", &from, &to,
+		    BUNYAN_T_UINT32, "hdrlen", (uint32_t)hdrlen,
+		    BUNYAN_T_UINT32, "pktlen", (uint32_t)pktlen);
+		return;
+	}
+
+	pkt = ikev2_pkt_new_inbound(buf, pktlen);
+	if (pkt == NULL)
+		return;
+
+	ikev2_dispatch(pkt, &to, &from);
+}
+
+static int
+udp_listener_socket(sa_family_t af, uint16_t port)
+{
+	struct sockaddr_storage storage = { 0 };
+	sockaddr_u_t sau = { .sau_ss = &storage };
+	size_t socksize = 0;
+	const char *afstring = NULL;
+	int sock = -1;
+	int yes = 1;
+	ipsec_req_t ipsr = { 0 };
+
+	ipsr.ipsr_ah_req = ipsr.ipsr_esp_req = IPSEC_PREF_NEVER;
+
+	switch (af) {
+	case AF_INET:
+		afstring = "AF_INET";
+		socksize = sizeof (struct sockaddr_in);
+		break;
+	case AF_INET6:
+		afstring = "AF_INET6";
+		socksize = sizeof (struct sockaddr_in6);
+		break;
+	default:
+		INVALID("af");
+	}
+
+	if ((sock = socket(af, SOCK_DGRAM, 0)) == -1)
+		err(EXIT_FAILURE, "%s: socket(%s)", __func__, afstring);
+
+	sau.sau_ss->ss_family = af;
+	/* Exploit that sin_port and sin6_port live at the same offset. */
+	sau.sau_sin->sin_port = htons(port);
+	if (bind(sock, (const struct sockaddr *)sau.sau_ss, socksize) == -1)
+		err(EXIT_FAILURE, "%s: bind(%s, %d)", __func__, afstring, port);
+
+	switch (af) {
+	case AF_INET:
+		/* Make sure we can receive the destination address */
+		if (setsockopt(sock, IPPROTO_IP, IP_RECVDSTADDR,
+		    (const void *)&yes, sizeof (yes)) == -1)
+			err(EXIT_FAILURE, "%s: setsockopt(IP_RECVDSTADDR)",
+			    __func__);
+
+		if (setsockopt(sock, IPPROTO_IP, IP_SEC_OPT,
+		    (const void *)&ipsr, sizeof (ipsr)) == -1)
+			err(EXIT_FAILURE, "%s: setsockopt(IP_SEC_OPT)",
+			    __func__);
+		break;
+	case AF_INET6:
+		if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVPKTINFO,
+		    (const void *)&yes, sizeof (yes)) == -1)
+			err(EXIT_FAILURE, "%s: setsockopt(IPV6_RECVPKTINFO)",
+			    __func__);
+
+		if (setsockopt(sock, IPPROTO_IPV6, IPV6_SEC_OPT,
+		    (const void *)&ipsr, sizeof (ipsr)) == -1)
+			err(EXIT_FAILURE, "%s: setsockopt(IPV6_SEC_OPT)",
+			    __func__);
+		break;
+	default:
+		INVALID(af);
+	}
+
+	/* Setup IPv4 NAT Traversal */
+	if (af == AF_INET && port == IPPORT_IKE_NATT) {
+		int nat_t = 1;
+
+		if (setsockopt(sock, IPPROTO_UDP, UDP_NAT_T_ENDPOINT,
+		    &nat_t, sizeof (nat_t)) == -1)
+			err(EXIT_FAILURE, "%s: setsockopt(IPPROTO_UDP, "
+			    "UDP_NAT_T_ENDPOINT", __func__);
+	}
+
+	return (sock);
+}
+
+static uchar_t *
+inbound_buf(void)
+{
+	uchar_t *ptr = pthread_getspecific(inbound_key);
+
+	if (ptr != NULL)
+		return (ptr);
+
+	if ((ptr = umem_alloc(MAX_PACKET_SIZE, UMEM_DEFAULT)) == NULL)
+		err(EXIT_FAILURE, "%s", __func__);
+
+	PTH(pthread_setspecific(inbound_key, ptr));
+	return (ptr);
+}
+
+static void
+inbound_free_buf(void *buf)
+{
+	umem_free(buf, MAX_PACKET_SIZE);
+}
+
+void
+inbound_init(void)
+{
+	PTH(pthread_key_create_once_np(&inbound_key, inbound_free_buf));
+
+	ikesock4 = udp_listener_socket(AF_INET, IPPORT_IKE);
+	nattsock = udp_listener_socket(AF_INET, IPPORT_IKE_NATT);
+	ikesock6 = udp_listener_socket(AF_INET6, IPPORT_IKE);
+
+	schedule_socket(ikesock4, inbound);
+	schedule_socket(nattsock, inbound);
+	schedule_socket(ikesock6, inbound);
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/inbound.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,32 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2015 Jason King.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _INBOUND_H
+#define	_INBOUND_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+extern int ikesock4, ikesock6, nattsock;
+
+void inbound_init(void);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _INBOUND_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/main.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,364 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#include <pthread.h>
+#include <errno.h>
+#include <err.h>
+#include <signal.h>
+#include <port.h>
+#include <bunyan.h>
+#include <string.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <locale.h>
+#include <ucontext.h>
+#include "pkcs11.h"
+#include "defs.h"
+#include "timer.h"
+#include "worker.h"
+#include "config.h"
+
+extern void pkt_init(void);
+extern void pkt_fini(void);
+extern void ikev2_sa_init(void);
+extern void random_init(void);
+extern void pfkey_init(void);
+
+static void signal_init(void);
+static void event(event_t, void *);
+static void do_signal(int);
+static void main_loop(void);
+
+static const char *port_source_str(ushort_t);
+static const char *event_str(event_t);
+
+static boolean_t done;
+static pthread_t signal_tid;
+
+bunyan_logger_t *log = NULL;
+int port = -1;
+
+static void
+usage(const char *name)
+{
+	(void) fprintf(stderr, "Usage: %s [-d] [-f cfgfile]\n"
+	    "      %s -c [-f cfgfile]\n", name, name);
+	exit(1);
+}
+
+int
+main(int argc, char **argv)
+{
+	FILE *f = NULL;
+	char *cfgfile = "/etc/inet/ike/config";
+	int c, rc;
+	boolean_t debug_mode = B_FALSE;
+	boolean_t check_cfg = B_FALSE;
+
+	(void) setlocale(LC_ALL, "");
+#if !defined(TEXT_DOMAIN)
+#define	TEXT_DOMAIN "SYS_TEST"
+#endif
+	(void) textdomain(TEXT_DOMAIN);
+
+	while ((c = getopt(argc, argv, "cdf:")) != -1) {
+		switch (c) {
+		case 'd':
+			debug_mode = B_TRUE;
+			break;
+		case 'c':
+			check_cfg = B_TRUE;
+			break;
+		case 'f':
+			cfgfile = optarg;
+			break;
+		case '?':
+			(void) fprintf(stderr,
+			    "Unrecognized option: -%c\n", optopt);
+			usage(argv[0]);
+			break;
+		}
+	}
+
+	if (check_cfg && debug_mode) {
+		(void) fprintf(stderr, "-d and -c options cannot both be "
+		    "set\n");
+		usage(argv[0]);
+		return (1);
+	}
+
+	if ((rc = bunyan_init(basename(argv[0]), &log)) < 0)
+		errx(EXIT_FAILURE, "bunyan_init() failed: %s", strerror(errno));
+
+	/* hard coded just during development */
+	if ((rc = bunyan_stream_add(log, "stdout", BUNYAN_L_TRACE,
+	    bunyan_stream_fd, (void *)STDOUT_FILENO)) < 0)
+		errx(EXIT_FAILURE, "bunyan_stream_add() failed: %s",
+		    strerror(errno));
+
+	if ((f = fopen(cfgfile, "rF")) == NULL)
+		STDERR(fatal, log, "cannot open config file",
+		    BUNYAN_T_STRING, "filename", cfgfile);
+
+	process_config(f, check_cfg, log);
+
+	(void) fclose(f);
+
+	if ((port = port_create()) < 0)
+		STDERR(fatal, log, "port_create() failed");
+
+	signal_init();
+	random_init();
+	pkcs11_init();
+	pkt_init();
+	ike_timer_init();
+	pfkey_init();
+
+	/* XXX: make these configurable */
+	worker_init(8, 8);
+
+	main_loop();
+
+	pkt_fini();
+	pkcs11_fini();
+
+	return (0);
+}
+
+static void
+main_loop(void)
+{
+	port_event_t pe;
+	int rc;
+
+	(void) bunyan_trace(log, "starting main loop", BUNYAN_T_END);
+
+	/*CONSTCOND*/
+	while (!done) {
+		if (port_get(port, &pe, NULL) < 0) {
+			STDERR(error, log, "port_get() failed");
+			continue;
+		}
+
+		switch (pe.portev_source) {
+		case PORT_SOURCE_USER:
+			(void) bunyan_trace(log, "received event",
+			    BUNYAN_T_STRING, "source",
+			    port_source_str(pe.portev_source),
+			    BUNYAN_T_UINT32, "source val",
+			    (uint32_t)pe.portev_events,
+			    BUNYAN_T_STRING, "event",
+			    event_str(pe.portev_events),
+			    BUNYAN_T_UINT32, "event num",
+			    (int32_t)pe.portev_events,
+			    BUNYAN_T_POINTER, "event arg", pe.portev_user,
+			    BUNYAN_T_END);
+			event(pe.portev_events, pe.portev_user);
+			break;
+
+		case PORT_SOURCE_FD: {
+			char buf[128] = { 0 };
+			(void) addrtosymstr(pe.portev_user, buf, sizeof (buf));
+
+			(void) bunyan_trace(log, "received event",
+			    BUNYAN_T_STRING, "source",
+			    port_source_str(pe.portev_source),
+			    BUNYAN_T_UINT32, "source val",
+			    (uint32_t)pe.portev_events,
+			    BUNYAN_T_INT32, "fd", (int32_t)pe.portev_object,
+			    BUNYAN_T_STRING, "cb", buf,
+			    BUNYAN_T_POINTER, "cbval", pe.portev_user,
+			    BUNYAN_T_END);
+
+			void (*fn)(int) = (void (*)(int))pe.portev_user;
+			int fd = (int)pe.portev_object;
+			fn(fd);
+			break;
+		}
+
+		case PORT_SOURCE_ALERT:
+			(void) bunyan_trace(log, "received event",
+			    BUNYAN_T_STRING, "source",
+			    port_source_str(pe.portev_source),
+			    BUNYAN_T_UINT32, "source val",
+			    (uint32_t)pe.portev_events,
+			    BUNYAN_T_END);
+			break;
+
+		default:
+			INVALID("pe.portev_source");
+		}
+	}
+
+	(void) bunyan_info(log, "Exiting", BUNYAN_T_END);
+}
+
+static void
+event(event_t evt, void *arg)
+{
+	switch (evt) {
+	case EVENT_NONE:
+		return;
+	case EVENT_SIGNAL:
+		do_signal((int)(uintptr_t)arg);
+		break;
+	}
+}
+
+void
+reload(void)
+{
+}
+
+void
+schedule_socket(int fd, void (*cb)(int, void *))
+{
+	if (port_associate(port, PORT_SOURCE_FD, fd, POLLIN, cb) < 0)
+		STDERR(error, log, "port_associate() failed");
+}
+
+static void
+do_signal(int signum)
+{
+	switch (signum) {
+	case SIGINT:
+	case SIGTERM:
+	case SIGQUIT:
+		done = B_TRUE;
+		break;
+	case SIGHUP:
+		reload();
+		break;
+	case SIGUSR1:
+		(void) worker_add();
+		break;
+	case SIGUSR2:
+		(void) worker_del();
+		break;
+	default:
+		break;
+	}
+}
+
+/*ARGSUSED*/
+static void *
+signal_thread(void *arg)
+{
+	char sigbuf[SIG2STR_MAX + 3]; /* add room for 'SIG' */
+	sigset_t sigset;
+	int signo, ret;
+
+	bunyan_trace(log, "signal thread awaiting signals", BUNYAN_T_END);
+
+	(void) sigfillset(&sigset);
+
+	/*CONSTCOND*/
+	while (1) {
+		if (sigwait(&sigset, &signo) != 0) {
+			STDERR(error, log, "sigwait() failed");
+			continue;
+		}
+
+		(void) memset(sigbuf, 0, sizeof (sigbuf));
+		(void) strlcat(sigbuf, "SIG", sizeof (sigbuf));
+		sig2str(signo, sigbuf + 3);
+
+		(void) bunyan_info(log, "signal received",
+		    BUNYAN_T_STRING, "signal", sigbuf,
+		    BUNYAN_T_INT32, "signum", (int32_t)signo,
+		    BUNYAN_T_END);
+
+		if (port_send(port, EVENT_SIGNAL, (void *)(uintptr_t)signo) < 0)
+			STDERR(error, log, "port_send() failed");
+	}
+
+	/*NOTREACHED*/
+	return (NULL);
+}
+
+static void
+signal_init(void)
+{
+	pthread_attr_t attr;
+	sigset_t nset;
+
+	/* block all signals in main thread */
+	(void) sigfillset(&nset);
+	(void) pthread_sigmask(SIG_SETMASK, &nset, NULL);
+
+	PTH(pthread_attr_init(&attr));
+	PTH(pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED));
+	PTH(pthread_create(&signal_tid, &attr, signal_thread, NULL));
+	PTH(pthread_attr_destroy(&attr));
+}
+
+static const char *
+port_source_str(ushort_t src)
+{
+#define	STR(x) case x: return (#x)
+
+	switch (src) {
+	STR(PORT_SOURCE_AIO);
+	STR(PORT_SOURCE_FD);
+	STR(PORT_SOURCE_MQ);
+	STR(PORT_SOURCE_TIMER);
+	STR(PORT_SOURCE_USER);
+	STR(PORT_SOURCE_ALERT);
+	STR(PORT_SOURCE_FILE);
+	default:
+		return ("UNKNOWN");
+	}
+#undef STR
+}
+
+static const char *
+event_str(event_t evt)
+{
+#define	STR(x) case x: return (#x)
+
+	switch (evt) {
+	STR(EVENT_NONE);
+	STR(EVENT_SIGNAL);
+	default:
+		return ("UNKNOWN");
+	}
+}
+
+bunyan_logfn_t
+getlog(bunyan_level_t level)
+{
+	switch (level) {
+	case BUNYAN_L_TRACE:
+		return (bunyan_trace);
+	case BUNYAN_L_DEBUG:
+		return (bunyan_debug);
+	case BUNYAN_L_INFO:
+		return (bunyan_info);
+	case BUNYAN_L_WARN:
+		return (bunyan_warn);
+	case BUNYAN_L_ERROR:
+		return (bunyan_error);
+	case BUNYAN_L_FATAL:
+		return (bunyan_fatal);
+        }
+
+	return (NULL);
+}
+
+extern uint32_t ss_port(const struct sockaddr_storage *);
+extern const void *ss_addr(const struct sockaddr_storage *);
+extern int ss_bunyan(const struct sockaddr_storage *);
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pfkey.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,769 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <net/pfkeyv2.h>
+#include <ipsec_util.h>
+#include <string.h>
+#include <errno.h>
+#include <locale.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <note.h>
+#include <atomic.h>
+#include <pthread.h>
+#include <sys/stropts.h>	/* For I_NREAD */
+#include <libuutil.h>
+#include <bunyan.h>
+
+#include "defs.h"
+#include "ikev2.h"
+#include "ikev2_pkt.h"
+#include "ikev2_sa.h"
+#include "pkcs11.h"
+
+struct pfreq;
+typedef struct pfreq {
+	uu_list_node_t	pr_node;
+	pfreq_cb_t	*pr_cb;
+	void		*pr_data;
+	uint32_t	pr_msgid;
+} pfreq_t;
+
+static bunyan_logger_t	*pflog;
+static uu_list_pool_t	*pfreq_pool;
+static umem_cache_t	*pfreq_cache;
+static pthread_mutex_t	pfreq_lock = PTHREAD_MUTEX_INITIALIZER;
+static uu_list_t	*pfreq_list;
+
+/* PF_KEY socket. */
+int pfkey;
+
+/* our msgids */
+static volatile uint32_t msgid = 0;
+
+static int pfreq_ctor(void *, void *, int);
+static pfreq_t *pfreq_new(pfreq_cb_t *, void *);
+static void pfreq_free(pfreq_t *);
+
+static void handle_reply(sadb_msg_t *);
+static void handle_delete(sadb_msg_t *);
+static void handle_flush(sadb_msg_t *);
+static void handle_expire(sadb_msg_t *);
+static void handle_acquire(sadb_msg_t *, boolean_t);
+static void handle_register(sadb_msg_t *);
+
+#if 0
+static ikev2_pay_sa_t *convert_acquire(parsedmsg_t *);
+static ikev2_pay_sa_t *convert_ext_acquire(parsedmsg_t *, ikev2_spi_proto_t);
+static ikev2_xf_auth_t ikev2_pf_to_auth(int);
+#endif
+
+static const char *pfkey_opcodes[] = {
+	"RESERVED", "GETSPI", "UPDATE", "ADD", "DELETE", "GET",
+	"ACQUIRE", "REGISTER", "EXPIRE", "FLUSH", "DUMP", "X_PROMISC",
+	"X_INVERSE_ACQUIRE", "X_UPDATEPAIR", "X_DELPAIR"
+};
+
+static const char *
+pfkey_type(unsigned int type)
+{
+	if (type > SADB_MAX)
+		return ("ILLEGAL");
+	else
+		return (pfkey_opcodes[type]);
+}
+
+static const char *pfkey_satypes[] = {
+	"UNSPEC", "<undef>", "AH", "ESP", "<undef>", "RSVP", "OSPFV2",
+	"RIPV2", "MIP"
+};
+
+static const char *
+pfkey_satype(unsigned int type)
+{
+	if (type > SADB_SATYPE_MAX)
+		return ("ILLEGAL");
+	else
+		return (pfkey_satypes[type]);
+}
+
+/* Deal with algorithm name lookups */
+
+static const char *
+alg_to_string(int doi_number, const algindex_t *algindex)
+{
+	int i;
+
+	for (i = 0; algindex[i].desc; i++)
+		if (doi_number == algindex[i].doi_num)
+			return (algindex[i].desc);
+	return ("unknown");
+}
+
+char *
+kef_alg_to_string(int algnum, int protonum, char *algname)
+{
+	struct ipsecalgent *testentry;
+	int error;
+
+	testentry = getipsecalgbynum(algnum, protonum, &error);
+	if (testentry == NULL || testentry->a_names[0] == NULL)
+		(void) snprintf(algname, 80, "unknown");
+	else
+		(void) snprintf(algname, 80, "%s", testentry->a_names[0]);
+
+	/* safe to use on a NULL pointer */
+	(void) freeipsecalgent(testentry);
+	return (algname);
+}
+
+/*
+ * The passed in parsedmsg_t looks like this (see defs.h):
+ *
+ * {
+ *	*pmsg_next
+ *	*pmsg_samsg
+ *	*pmsg_exts[0][1][2][3].....[SADB_EXT_MAX + 2]
+ *	*pmsg_sss  (struct sockaddr_storage *)
+ *	*pmsg_dss  (struct sockaddr_storage *)
+ *	*pmsg_isss (struct sockaddr_storage *)
+ *	*pmsg_idss (struct sockaddr_storage *)
+ *	*pmsg_nlss (struct sockaddr_storage *)
+ *	*pmsg_nrss (struct sockaddr_storage *)
+ * } parsedmsg_t;
+ *
+ * This function parses through the whole samsg looking for valid PF_KEY
+ * extensions. Each extension type found is saved in the pmsg_exts array.
+ * As the parsedmsg_t is initialised as zero's when entering the function, it's
+ * easy to check later to see which extensions exist in the samsg by
+ * checking for NULL.
+ *
+ * Some extensions will have a sockaddr_storage associated with the type
+ * EG: SADB_EXT_ADDRESS_SRC, in these cases a pointer to the appropriate
+ * structure in samsg is set in the parsedmsg_t.
+ *
+ * After parsing the whole samsg, the optional arguments (which is a list
+ * of required extensions) are checked for in the parsedmsg_t. If all of the
+ * required extensions are valid then the function returns B_TRUE.
+ *
+ * Even if the required extensions are not in the samsg (and the function
+ * returns B_FALSE) the pmsg->pmsg_exts array will still contain the headers
+ * that were in the samsg.
+ *
+ * Assume the kernel knows what it's doing with messages that get passed up.
+ * The variable arguments are a list of ints with SADB_EXT_* values.
+ */
+static boolean_t
+extract_exts(sadb_msg_t *samsg, parsedmsg_t *pmsg, int numexts, ...)
+{
+	sadb_ext_t *ext;
+	sadb_ext_t **exts = pmsg->pmsg_exts;
+	int current_ext;
+	va_list ap;
+	boolean_t rc = B_TRUE;
+
+	(void) memset(pmsg, 0, sizeof (parsedmsg_t));
+
+	ext = (sadb_ext_t *)(samsg + 1);
+	pmsg->pmsg_samsg = samsg;
+
+	do {
+		exts[ext->sadb_ext_type] = ext;
+		if (ext->sadb_ext_type == SADB_EXT_ADDRESS_SRC)
+			pmsg->pmsg_sss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+		if (ext->sadb_ext_type == SADB_EXT_ADDRESS_DST)
+			pmsg->pmsg_dss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+		if (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_SRC)
+			pmsg->pmsg_isss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+		if (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_INNER_DST)
+			pmsg->pmsg_idss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+		if (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_NATT_REM)
+			pmsg->pmsg_nrss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+		if (ext->sadb_ext_type == SADB_X_EXT_ADDRESS_NATT_LOC)
+			pmsg->pmsg_nlss = (struct sockaddr_storage *)
+			    (((sadb_address_t *)ext) + 1);
+
+		ext = (sadb_ext_t *)(((uint64_t *)ext) + ext->sadb_ext_len);
+
+	} while (((uint8_t *)ext) - ((uint8_t *)samsg) <
+	    SADB_64TO8(samsg->sadb_msg_len));
+
+	va_start(ap, numexts);
+	while (numexts-- > 0) {
+		current_ext = va_arg(ap, int);
+		if (exts[current_ext] == NULL) {
+			rc = B_FALSE;
+			break;
+		}
+	}
+	va_end(ap);
+
+	return (rc);
+}
+
+static void
+pfkey_inbound(int s, void *arg)
+{
+	_NOTE(ARGUNUSED(arg))
+
+	sadb_msg_t *samsg;
+	ssize_t rc;
+	int length;
+
+	if (ioctl(s, I_NREAD, &length) < 0) {
+		STDERR(error, log, "ioctl(I_NREAD) failed");
+
+		/* XXX KEBE ASKS - will we rapidly return at this point? */
+		schedule_socket(s, pfkey_inbound);
+		return;
+	}
+
+	if (length == 0) {
+		bunyan_info(log, "ioctl: zero length message",
+		    BUNYAN_T_STRING, "func", __func__,
+		    BUNYAN_T_STRING, "file", __FILE__,
+		    BUNYAN_T_INT32, "line", __LINE__,
+		    BUNYAN_T_END);
+		/* XXX KEBE ASKS - will we rapidly return at this point? */
+		schedule_socket(s, pfkey_inbound);
+		return;
+	}
+
+	samsg = malloc(length);
+	if (samsg == NULL) {
+		STDERR(error, log, "malloc failure");
+		schedule_socket(s, pfkey_inbound);
+		return;
+	}
+
+	rc = read(s, samsg, length);
+	if (rc <= 0) {
+		if (rc == -1) {
+			STDERR(error, log, "read failed");
+			/* Should I exit()? */
+		}
+		free(samsg);
+		/* XXX KEBE ASKS - will we rapidly return at this point? */
+		schedule_socket(s, pfkey_inbound);
+		return;
+	}
+
+	/* At this point, we can safely re-schedule the socket for reading. */
+	schedule_socket(s, pfkey_inbound);
+
+	bunyan_debug(log, "SADB message received",
+	    BUNYAN_T_STRING, "msg_type", pfkey_type(samsg->sadb_msg_type),
+	    BUNYAN_T_UINT32, "msg_type_val", (uint32_t)samsg->sadb_msg_type,
+	    BUNYAN_T_STRING, "sa_type", pfkey_satype(samsg->sadb_msg_satype),
+	    BUNYAN_T_UINT32, "sa_type_val", (uint32_t)samsg->sadb_msg_satype,
+	    BUNYAN_T_UINT32, "msg_pid", samsg->sadb_msg_pid,
+	    BUNYAN_T_UINT32, "msg_seq", samsg->sadb_msg_seq,
+	    BUNYAN_T_UINT32, "msg_errno_val", (uint32_t)samsg->sadb_msg_errno,
+	    BUNYAN_T_STRING, "msg_errno", strerror(samsg->sadb_msg_errno),
+	    BUNYAN_T_UINT32, "msg_diagnostic_val",
+	    (uint32_t)samsg->sadb_x_msg_diagnostic,
+	    BUNYAN_T_UINT32, "msg_diagnostic",
+	    keysock_diag(samsg->sadb_x_msg_diagnostic),
+	    BUNYAN_T_UINT32, "length", (uint32_t)samsg->sadb_msg_len,
+	    BUNYAN_T_END);
+
+	/*
+	 * XXX KEBE SAYS for now don't print the full inbound message.  An
+	 * "ipseckey monitor" instance is useful here.
+	 */
+
+	/*
+	 * If it might be a reply to us, handle it.
+	 */
+	if (samsg->sadb_msg_pid == getpid()) {
+		handle_reply(samsg);
+		return;
+	}
+
+	/*
+	 * Silently pitch the message if it's an error reply to someone else.
+	 */
+	if (samsg->sadb_msg_errno != 0) {
+		bunyan_debug(log, "reply not for us, dropped", BUNYAN_T_END);
+		free(samsg);
+		return;
+	}
+
+	/*
+	 * At this point, we have a kernel-emitted message.  Typically this
+	 * will be an ACQUIRE, EXPIRE, or DELETE/DELPAIR.  Other ones include
+	 * FLUSH (which if we follow in.iked semantics, will nuke all IKE SA
+	 * state) and REGISTER (indicating a change in kernel algorithm
+	 * support).
+	 */
+
+	switch (samsg->sadb_msg_type) {
+	case SADB_ACQUIRE:
+		handle_acquire(samsg, B_TRUE);
+		return;
+	case SADB_EXPIRE:
+		handle_expire(samsg);
+		return;
+	case SADB_DELETE:
+	case SADB_X_DELPAIR:
+		handle_delete(samsg);
+		return;
+	case SADB_FLUSH:
+		handle_flush(samsg);
+		return;
+	case SADB_REGISTER:
+		handle_register(samsg);
+		/*
+		 * Explicitly free it here because handle_register() is also
+		 * called from pf_key_init(), which has samsg on the stack
+		 * instead.
+		 */
+		free(samsg);
+		return;
+	}
+
+	bunyan_debug(log, "SADB message type unknown, ignored.",
+	    BUNYAN_T_UINT32, "msg_type_val", (uint32_t)samsg->sadb_msg_type,
+	    BUNYAN_T_END);
+	free(samsg);
+}
+
+/*
+ * Send a pfkey message 'msg'.  The reply will invoke the callback
+ * function 'cb' with data as an argument.
+ * Returns B_TRUE if the request is successfully sent,
+ * B_FALSE if there was an error.
+ */
+boolean_t
+pfkey_send_msg(sadb_msg_t *msg, pfreq_cb_t *cb, void *data)
+{
+	pfreq_t *req;
+	uu_list_index_t idx;
+	ssize_t n;
+
+	req = pfreq_new(cb, data);
+	if (req == NULL)
+		return (B_FALSE);
+
+	msg->sadb_msg_seq = req->pr_msgid;
+	msg->sadb_msg_pid = getpid();
+	n = write(pfkey, msg, msg->sadb_msg_len);
+	if (n != msg->sadb_msg_len) {
+		if (n < 0) {
+			STDERR(error, log, "pf_key write failed");
+		} else {
+			bunyan_error(log, "pf_key truncated write",
+			    BUNYAN_T_UINT32, "n", (uint32_t)n,
+			    BUNYAN_T_END);
+		}
+		pfreq_free(req);
+		return (B_FALSE);
+	}
+
+	(void) pthread_mutex_lock(&pfreq_lock);
+
+	(void) uu_list_find(pfreq_list, req, NULL, &idx);
+	uu_list_insert(pfreq_list, req, idx);
+	(void) pthread_mutex_unlock(&pfreq_lock);
+
+	return (B_TRUE);
+}
+
+/*
+ * Inform the kernel of an error.
+ * src is the pfkey message the error is a response to, reason is
+ * the reason for the error
+ */
+void
+pfkey_send_error(const sadb_msg_t *src, uint8_t reason)
+{
+	sadb_msg_t msg = { 0 };
+	ssize_t n;
+
+	/* Errors consists of just the sadb header */
+	msg.sadb_msg_len = SADB_8TO64(sizeof (sadb_msg_t));
+	msg.sadb_msg_version = PF_KEY_V2;
+	msg.sadb_msg_type = src->sadb_msg_type;
+	msg.sadb_msg_errno = reason;
+	msg.sadb_msg_satype = src->sadb_msg_satype;
+	msg.sadb_x_msg_diagnostic = SADB_X_DIAGNOSTIC_NONE;
+	msg.sadb_msg_seq = src->sadb_msg_seq;
+	msg.sadb_msg_pid = src->sadb_msg_pid;
+
+	n = write(pfkey, &msg, sizeof (sadb_msg_t));
+	if (n != sizeof (sadb_msg_t))
+		STDERR(error, log, "Unable to send PFKEY error notification");
+}
+
+static void
+handle_reply(sadb_msg_t *reply)
+{
+	pfreq_t *req;
+	uu_list_index_t idx;
+
+	PTH(pthread_mutex_lock(&pfreq_lock));
+	req = uu_list_find(pfreq_list, NULL, reply, &idx);
+	if (req != NULL)
+		uu_list_remove(pfreq_list, req);
+	PTH(pthread_mutex_unlock(&pfreq_lock));
+
+	if (req == NULL) {
+		/* XXX: log more fields? */
+		bunyan_info(pflog, "Received a reply to an unknown request; "
+		    "ignorning.",
+		    BUNYAN_T_STRING, "msg_type",
+		    pfkey_satype(reply->sadb_msg_type),
+		    BUNYAN_T_UINT32, "msg_type_val",
+		    (uint32_t)reply->sadb_msg_type,
+		    BUNYAN_T_INT32, "pid", reply->sadb_msg_pid,
+		    BUNYAN_T_UINT32, "seq", reply->sadb_msg_seq,
+		    BUNYAN_T_END);
+		free(reply);
+		return;
+	}
+
+#if 0
+	req->pr_cb(reply, req->pr_data);
+	pfreq_free(req);
+#endif
+
+	switch (reply->sadb_msg_type) {
+	case SADB_ACQUIRE:
+	{
+		/* Should be a response to our inverse acquire */
+		parsedmsg_t pmsg = { 0 };
+
+		if (!extract_exts(reply, &pmsg, 1, SADB_X_EXT_EPROP)) {
+			bunyan_info(pflog, "No extended proposal found in "
+			    "ACQUIRE reply.", BUNYAN_T_END);
+			free(reply);
+			return;
+		}
+
+		/*
+		 * XXX: lookup what we queried based on msgid, extract
+		 * SA type and pass to convert_ext_acquire
+		 */
+
+		/* XXX: continue CHILD SA processing */
+		break;
+	}
+	default:
+		/* XXX: More to come */
+		;
+	}
+
+	free(reply);
+}
+
+static void
+handle_flush(sadb_msg_t *samsg)
+{
+	bunyan_trace(pflog, "Handling SADB flush message", BUNYAN_T_END);
+
+	/* Return if just AH or ESP SAs are being freed. */
+	if (samsg->sadb_msg_satype != SADB_SATYPE_UNSPEC)
+		return;
+
+	/* XXX KEBE SAYS FILL ME IN! */
+
+	ikev2_sa_flush();
+
+	/*
+	 * If we receive an SADB_FLUSH for all SA types, get rid of any IKE
+	 * SAs.
+	 */
+
+	free(samsg);
+}
+
+/*
+ * Handle the PF_KEY SADB_EXPIRE message for idle timeout.
+ *
+ * XXX KEBE SAYS this'll most likely kick off Dead Peer Detection if we can
+ * find an IKE SA.
+ */
+static void
+handle_idle_timeout(sadb_msg_t *samsg)
+{
+	bunyan_trace(pflog, "Handling SADB idle expire message", BUNYAN_T_END);
+
+	/* XXX KEBE SAYS FILL ME IN! */
+	free(samsg);
+}
+
+static void
+handle_expire(sadb_msg_t *samsg)
+{
+	parsedmsg_t pmsg;
+
+	/*
+	 * If SOFT expire, see if the SADB_X_SAFLAGS_KM1 (initiator) is set,
+	 * if so, consider treating this expire as an ACQUIRE message.
+	 *
+	 * If HARD expire, treat this message like a DELETE.
+	 *
+	 * If IDLE expire, see if we need to do a little DPD or not.
+	 */
+
+	if (extract_exts(samsg, &pmsg, 1, SADB_EXT_LIFETIME_HARD)) {
+		bunyan_debug(pflog, "Handling SADB hard expire message",
+		    BUNYAN_T_END);
+		handle_delete(samsg);
+		return;
+	}
+
+	if (pmsg.pmsg_exts[SADB_X_EXT_LIFETIME_IDLE] != NULL) {
+		handle_idle_timeout(samsg);
+		return;
+	}
+
+	/*
+	 * extract_exts() has already filled in pmsg with data from
+	 * samsg. pmsg.pmsg_exts[foo] will be NULL if this was
+	 * not set in samsg. Bail out if the message appears to be
+	 * poorly formed. If everything looks good, create a new
+	 * "ACQUIRE like" message and pass off to handle_acquire().
+	 */
+
+	if (pmsg.pmsg_exts[SADB_EXT_LIFETIME_SOFT] == NULL) {
+		/* XXX: more fields */
+		bunyan_info(pflog, "SADB EXPIRE message is missing both "
+		    "hard and soft lifetimes", BUNYAN_T_END);
+		/* XXX: ignore? */
+	}
+
+	bunyan_debug(pflog, "Handling SADB soft expire message", BUNYAN_T_END);
+	/* XXX KEBE SAYS FILL ME IN! */
+
+	free(samsg);
+}
+
+static void
+handle_register(sadb_msg_t *samsg)
+{
+	_NOTE(ARGUNUSED(samsg))
+	/* XXX KEBE SAYS FILL ME IN! */
+
+	/*
+	 * XXX KEBE wonders if this is as necessary as 2367 first imagined?
+	 *
+	 * With inverse-ACQUIRE, you know better what algorithms are available
+	 * and acceptable.  This may go away or remain a mere stub.
+	 */
+}
+
+static void
+handle_delete(sadb_msg_t *samsg)
+{
+	bunyan_trace(pflog, "Handling SADB delete", BUNYAN_T_END);
+
+	/* XXX KEBE SAYS FILL ME IN! */
+	free(samsg);
+}
+
+/*
+ * Handle a PF_KEY ACQUIRE message.  This function, or something that it
+ * calls (either directly or via callbacks) must free samsg.
+ */
+/* XXX KEBE wonders if create_child_sa will be needed here or not. */
+static void
+handle_acquire(sadb_msg_t *samsg, boolean_t create_child_sa)
+{
+	/* XXX: for now */
+	_NOTE(ARGUNUSED(create_child_sa))
+
+	parsedmsg_t pmsg;
+
+	bunyan_debug(pflog, "Handling SADB acquire", BUNYAN_T_END);
+
+	if (!extract_exts(samsg, &pmsg, 1, SADB_EXT_PROPOSAL)) {
+		bunyan_info(pflog, "No proposal found in ACQUIRE message",
+		    BUNYAN_T_END);
+		free(samsg);
+		return;
+	}
+
+	/* XXX KEBE SAYS FILL ME IN! */
+	free(samsg);
+}
+
+static int
+pfreq_compare(const void *l_arg, const void *r_arg, void *msg_arg)
+{
+	const pfreq_t *l = l_arg;
+	const pfreq_t *r = r_arg;
+	sadb_msg_t *msg = msg_arg;
+	uint32_t msgid;
+	
+	ASSERT((r != NULL && msg == NULL) || (r == NULL && msg != NULL));
+
+	if (r != NULL)
+		msgid = r->pr_msgid;
+	else
+		msgid = msg->sadb_msg_seq;
+
+	if (l->pr_msgid < msgid)
+		return (-1);
+	if (l->pr_msgid > msgid)
+		return (1);
+	if (r == NULL)
+		return (0);
+
+	if (l->pr_data < r->pr_data)
+		return (-1);
+	if (l->pr_data > r->pr_data)
+		return (1);
+	return (0);
+}
+
+static int
+pfreq_ctor(void *buf, void *ignore, int flags)
+{
+	_NOTE(ARGUNUSED(ignore, flags))
+	(void) memset(buf, 0, sizeof (pfreq_t));
+	return (0);
+}
+
+static pfreq_t *
+pfreq_new(pfreq_cb_t *cb, void *data)
+{
+	pfreq_t *req = umem_cache_alloc(pfreq_cache, UMEM_DEFAULT);
+
+	if (req == NULL)
+		return (NULL);
+
+	req->pr_msgid = atomic_inc_32_nv(&msgid);
+	req->pr_cb = cb;
+	req->pr_data = data;
+	return (req);
+}
+
+static void
+pfreq_free(pfreq_t *req)
+{
+	(void) pfreq_ctor(req, NULL, 0);
+	umem_cache_free(pfreq_cache, req);
+}
+
+static void
+pfkey_register(uint8_t satype)
+{
+	uint64_t buffer[128] = { 0 };
+	sadb_msg_t *samsg = (sadb_msg_t *)buffer;
+	ssize_t n;
+	uint32_t msgid = atomic_inc_32_nv(&msgid);
+	pid_t pid = getpid();
+
+	CTASSERT(sizeof (buffer) >= sizeof (*samsg));
+
+	samsg->sadb_msg_version = PF_KEY_V2;
+	samsg->sadb_msg_type = SADB_REGISTER;
+	samsg->sadb_msg_errno = 0;
+	samsg->sadb_msg_satype = satype;
+	samsg->sadb_msg_reserved = 0;
+	samsg->sadb_msg_seq = msgid;
+	samsg->sadb_msg_pid = pid;
+	samsg->sadb_msg_len = SADB_8TO64(sizeof (*samsg));
+
+	n = write(pfkey, buffer, sizeof (*samsg));
+	if (n < 0)
+		err(EXIT_FAILURE, "pf_key write error");
+	if (n < sizeof (*samsg))
+		errx(EXIT_FAILURE, "Unable to write pf_key register message");
+
+	do {
+		(void) memset(buffer, 0, sizeof (buffer));
+		n = read(pfkey, buffer, sizeof (buffer));
+		if (n < 0)
+			err(EXIT_FAILURE, "pf_key read failure");
+	} while (samsg->sadb_msg_seq != msgid ||
+	    samsg->sadb_msg_pid != pid ||
+	    samsg->sadb_msg_type != SADB_REGISTER);
+
+	if (samsg->sadb_msg_errno != 0) {
+		if (samsg->sadb_msg_errno != EPROTONOSUPPORT)
+			errx(EXIT_FAILURE, "pf_key register returned %s (%d).",
+			    strerror(samsg->sadb_msg_errno),
+			    samsg->sadb_msg_errno);
+		bunyan_error(pflog, "Protocol not supported",
+		    BUNYAN_T_UINT32, "msg_satype",
+		    (uint32_t)samsg->sadb_msg_satype, BUNYAN_T_END);
+	}
+
+	bunyan_debug(pflog, "Initial REGISTER with SADB",
+	    BUNYAN_T_STRING, "satype", pfkey_satype(samsg->sadb_msg_satype),
+	    BUNYAN_T_END);
+
+	handle_register(samsg);
+}
+
+void
+pfkey_init(void)
+{
+	uint32_t flag = 0;
+	int rc;
+
+#ifdef DEBUG
+	flag |= UU_LIST_POOL_DEBUG;
+#endif
+
+	pfreq_pool = uu_list_pool_create("pfreq_list", sizeof (pfreq_t),
+	    offsetof(pfreq_t, pr_node), pfreq_compare, flag);
+	if (pfreq_pool == NULL)
+		errx(EXIT_FAILURE, "Unable to create pfreq list pool: %s",
+		    uu_strerror(uu_error()));
+
+	pfreq_cache = umem_cache_create("pfreq cache", sizeof (pfreq_t),
+	    sizeof (uint64_t), pfreq_ctor, NULL, NULL, NULL, NULL, 0);
+	if (pfreq_cache == NULL)
+		err(EXIT_FAILURE, "Unable to create pfreq cache");
+
+	pfkey = socket(PF_KEY, SOCK_RAW, PF_KEY_V2);
+	if (pfkey == -1)
+		err(EXIT_FAILURE, "Unable to create pf_key socket");
+
+	rc = bunyan_child(log, &pflog,
+	    BUNYAN_T_INT32, "socketfd", (int32_t)pfkey,
+	    BUNYAN_T_END);
+	if (rc != 0)
+		errx(EXIT_FAILURE, "Unable to create child logger: %s",
+		    strerror(rc));
+
+	pfkey_register(SADB_SATYPE_ESP);
+	pfkey_register(SADB_SATYPE_AH);
+
+	schedule_socket(pfkey, pfkey_inbound);
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pkcs11.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,603 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#include <syslog.h>
+#include <assert.h>
+#include <string.h>
+#include <ipsec_util.h>
+#include <locale.h>
+#include <security/cryptoki.h>
+#include <pthread.h>
+#include <sys/debug.h>
+#include <note.h>
+#include "pkcs11.h"
+#include "defs.h"
+
+/*
+ * per usr/src/lib/pkcs11/libpkcs11/common/metaGlobal.h, the metaslot
+ * is always slot 0
+ */
+#define	METASLOT_ID	(0)
+
+CK_INFO			pkcs11_info = { 0 };
+CK_SESSION_HANDLE	p11h = CK_INVALID_HANDLE;
+
+#define	PKCS11_FUNC		"func"
+#define	PKCS11_RC		"errnum"
+#define	PKCS11_ERRMSG		"err"
+
+static void fmtstr(char *, size_t, CK_UTF8CHAR *, size_t);
+static CK_RV pkcs11_callback_handler(CK_SESSION_HANDLE, CK_NOTIFICATION,
+    void *);
+static void log_slotinfo(CK_SLOT_ID);
+
+/*
+ * Locates the metaslot among the available slots.  If the metaslot
+ * is inable to be located, we terminate.
+ */
+void
+pkcs11_init(void)
+{
+	CK_RV			rv = CKR_OK;
+	CK_ULONG 		nslot = 0;
+	CK_C_INITIALIZE_ARGS	args = {
+		NULL_PTR,		/* CreateMutex */
+		NULL_PTR,		/* DestroyMutex */
+		NULL_PTR,		/* LockMutex */
+		NULL_PTR,		/* UnlockMutex */
+		CKF_OS_LOCKING_OK,	/* flags */
+		NULL_PTR		/* reserved */
+	};
+
+	if ((rv = C_Initialize(&args)) != CKR_OK) {
+		PKCS11ERR(fatal, log, "C_Initialize", rv);
+		exit(1);
+	}
+
+	if ((rv = C_GetInfo(&pkcs11_info)) != CKR_OK) {
+		PKCS11ERR(fatal, log, "C_Info", rv);
+		exit(1);
+	}
+
+	if ((rv = C_GetSlotList(CK_FALSE, NULL, &nslot)) != CKR_OK) {
+		PKCS11ERR(fatal, log, "C_GetSlotList", rv);
+		exit(1);
+	}
+
+	CK_SLOT_ID slots[nslot];
+
+	if ((rv = C_GetSlotList(CK_FALSE, slots, &nslot)) != CKR_OK) {
+		PKCS11ERR(fatal, log, "C_GetSlotList", rv);
+		exit(1);
+	}
+
+	{
+		char manf[33];
+		char libdesc[33];
+
+		fmtstr(manf, sizeof (manf), pkcs11_info.manufacturerID,
+		    sizeof (pkcs11_info.manufacturerID));
+		fmtstr(libdesc, sizeof (libdesc),
+		    pkcs11_info.libraryDescription,
+		    sizeof (pkcs11_info.libraryDescription));
+
+		(void) bunyan_debug(log, "PKCS#11 provider info",
+		    BUNYAN_T_STRING, "manufacturer", manf,
+		    BUNYAN_T_UINT32, "version.major",
+		    (uint32_t)pkcs11_info.cryptokiVersion.major,
+		    BUNYAN_T_UINT32, "version.minor",
+		    (uint32_t)pkcs11_info.cryptokiVersion.minor,
+		    BUNYAN_T_UINT64, "flags",
+		    (uint64_t)pkcs11_info.flags,
+		    BUNYAN_T_STRING, "library", libdesc,
+		    BUNYAN_T_UINT32, "lib.major",
+		    (uint32_t)pkcs11_info.libraryVersion.major,
+		    BUNYAN_T_UINT32, "lib.minor",
+		    (uint32_t)pkcs11_info.libraryVersion.minor,
+		    BUNYAN_T_UINT32, "numslots", nslot,
+		    BUNYAN_T_END);
+	}
+
+	for (size_t i = 0; i < nslot; i++)
+		log_slotinfo(slots[i]);
+
+	rv = C_OpenSession(METASLOT_ID, CKF_SERIAL_SESSION, NULL,
+	    pkcs11_callback_handler, &p11h);
+	if (rv != CKR_OK) {
+		PKCS11ERR(fatal, log, "C_OpenSession", rv);
+		exit(1);
+	}
+
+	(void) bunyan_trace(log, "PKCS#11 session opened",
+	    BUNYAN_T_UINT64, "pkcs11 handle", (uint64_t)p11h,
+	    BUNYAN_T_END);
+}
+
+static void
+log_slotinfo(CK_SLOT_ID slot)
+{
+	CK_SLOT_INFO info = { 0 };
+	char manuf[33]; /* sizeof info.manufacturerID NUL */
+	CK_RV rv;
+
+	rv = C_GetSlotInfo(slot, &info);
+	if (rv != CKR_OK) {
+		PKCS11ERR(error, log, "C_GetSlotInfo", rv);
+		return;
+	}
+
+	{
+		char desc[65];	/* sizeof info.description + NUL */
+		fmtstr(desc, sizeof (desc), info.slotDescription,
+		    sizeof (info.slotDescription));
+		fmtstr(manuf, sizeof (manuf), info.manufacturerID,
+		    sizeof (info.manufacturerID));
+
+		(void) bunyan_debug(log, "PKCS#11 slot Info",
+		    BUNYAN_T_UINT64, "slot", (uint64_t)slot,
+		    BUNYAN_T_STRING, "desc", desc,
+		    BUNYAN_T_STRING, "manufacturer", manuf,
+		    BUNYAN_T_UINT32, "hwversion.major",
+		    (uint32_t)info.hardwareVersion.major,
+		    BUNYAN_T_UINT32, "hwversion.minor",
+		    (uint32_t)info.hardwareVersion.minor,
+		    BUNYAN_T_UINT32, "fwversion.major",
+		    (uint32_t)info.firmwareVersion.major,
+		    BUNYAN_T_UINT32, "fwversion.minor",
+		    (uint32_t)info.firmwareVersion.minor,
+		    BUNYAN_T_UINT64, "flags", (uint64_t)info.flags,
+		    BUNYAN_T_BOOLEAN, "present",
+		    !!(info.flags & CKF_TOKEN_PRESENT),
+		    BUNYAN_T_BOOLEAN, "removable",
+		    !!(info.flags & CKF_REMOVABLE_DEVICE),
+		    BUNYAN_T_BOOLEAN, "hwslot", !!(info.flags & CKF_HW_SLOT),
+		    BUNYAN_T_END);
+	}
+
+	if (!(info.flags & CKF_TOKEN_PRESENT))
+		return;
+
+	CK_TOKEN_INFO tinfo = { 0 };
+	rv = C_GetTokenInfo(slot, &tinfo);
+	if (rv != CKR_OK)
+		PKCS11ERR(error, log, "C_GetTokenInfo", rv);
+
+	char label[33];		/* sizeof tinfo.label + NUL */
+	char model[17];		/* sizeof tinfo.model + NUL */
+	char serial[17];	/* sizeof tinfo.serialNumber + NUL */
+	char utctime[17];	/* sizeof tinfo.utsTime + NUL */
+
+	fmtstr(manuf, sizeof (manuf), tinfo.manufacturerID,
+	    sizeof (tinfo.manufacturerID));
+	fmtstr(label, sizeof (label), tinfo.label, sizeof (tinfo.label));
+	fmtstr(model, sizeof (model), tinfo.model, sizeof (tinfo.model));
+	fmtstr(serial, sizeof (serial), tinfo.serialNumber,
+	    sizeof (tinfo.serialNumber));
+	fmtstr(utctime, sizeof (utctime), tinfo.utcTime,
+	    sizeof (tinfo.utcTime));
+
+#define	F(_inf, _flg) BUNYAN_T_BOOLEAN, #_flg, !!((_inf).flags & (_flg))
+
+	(void) bunyan_debug(log, "PKCS#11 token info",
+	    BUNYAN_T_UINT32, "slot", (uint32_t)slot,
+	    BUNYAN_T_STRING, "label", label,
+	    BUNYAN_T_STRING, "manuf", manuf,
+	    BUNYAN_T_STRING, "model", model,
+	    BUNYAN_T_STRING, "serial", serial,
+	    BUNYAN_T_UINT64, "flags", (uint64_t)tinfo.flags,
+	    F(info, CKF_RNG),
+	    F(info, CKF_WRITE_PROTECTED),
+	    F(info, CKF_LOGIN_REQUIRED),
+	    F(info, CKF_USER_PIN_INITIALIZED),
+	    F(info, CKF_RESTORE_KEY_NOT_NEEDED),
+	    F(info, CKF_CLOCK_ON_TOKEN),
+	    F(info, CKF_PROTECTED_AUTHENTICATION_PATH),
+	    F(info, CKF_DUAL_CRYPTO_OPERATIONS),
+	    F(info, CKF_TOKEN_INITIALIZED),
+	    F(info, CKF_SECONDARY_AUTHENTICATION),
+	    F(info, CKF_USER_PIN_COUNT_LOW),
+	    F(info, CKF_USER_PIN_FINAL_TRY),
+	    F(info, CKF_USER_PIN_LOCKED),
+	    F(info, CKF_USER_PIN_TO_BE_CHANGED),
+	    F(info, CKF_SO_PIN_COUNT_LOW),
+	    F(info, CKF_SO_PIN_FINAL_TRY),
+	    F(info, CKF_SO_PIN_LOCKED),
+	    F(info, CKF_SO_PIN_TO_BE_CHANGED),
+	    F(info, CKF_ERROR_STATE),
+	    BUNYAN_T_END);
+#undef F
+}
+
+void
+pkcs11_fini(void)
+{
+	CK_RV rv;
+
+	rv = C_CloseSession(p11h);
+	if (rv != CKR_OK)
+		PKCS11ERR(error, log, "C_CloseSession", rv);
+
+	rv = C_Finalize(NULL_PTR);
+	if (rv != CKR_OK)
+		PKCS11ERR(error, log, "C_Finalize", rv);
+}
+
+/*
+ * We explicitly avoid using the default: case in these switch statements
+ * so that the addition of new IKEv2 encryption algs will cause compilation
+ * errors if they are not added to these functions.
+ */
+CK_MECHANISM_TYPE
+ikev2_encr_to_p11(ikev2_xf_encr_t encr)
+{
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL_AES_GMAC:
+	case IKEV2_ENCR_NULL:
+	case IKEV2_ENCR_3IDEA:
+	case IKEV2_ENCR_XTS_AES:
+		INVALID("encr");
+		/*NOTREACHED*/
+		return (0);
+	case IKEV2_ENCR_DES_IV64:
+	case IKEV2_ENCR_DES:
+	case IKEV2_ENCR_DES_IV32:
+		return (CKM_DES_CBC);
+	case IKEV2_ENCR_3DES:
+		return (CKM_DES3_CBC);
+	case IKEV2_ENCR_RC5:
+		return (CKM_RC5_CBC);
+	case IKEV2_ENCR_IDEA:
+		return (CKM_IDEA_CBC);
+	case IKEV2_ENCR_CAST:
+		return (CKM_CAST5_CBC);
+	case IKEV2_ENCR_BLOWFISH:
+		return (CKM_BLOWFISH_CBC);
+	case IKEV2_ENCR_RC4:
+		return (CKM_RC4);
+	case IKEV2_ENCR_AES_CBC:
+		return (CKM_AES_CBC);
+	case IKEV2_ENCR_AES_CTR:
+		return (CKM_AES_CTR);
+	case IKEV2_ENCR_AES_CCM_8:
+	case IKEV2_ENCR_AES_CCM_12:
+	case IKEV2_ENCR_AES_CCM_16:
+		return (CKM_AES_CCM);
+	case IKEV2_ENCR_AES_GCM_8:
+	case IKEV2_ENCR_AES_GCM_12:
+	case IKEV2_ENCR_AES_GCM_16:
+		return (CKM_AES_GCM);
+	case IKEV2_ENCR_CAMELLIA_CBC:
+		return (CKM_CAMELLIA_CBC);
+	case IKEV2_ENCR_CAMELLIA_CTR:
+		return (CKM_CAMELLIA_CTR);
+	case IKEV2_ENCR_CAMELLIA_CCM_8:
+	case IKEV2_ENCR_CAMELLIA_CCM_12:
+	case IKEV2_ENCR_CAMELLIA_CCM_16:
+		return (CKM_CAMELLIA_CBC);
+	}
+	/*NOTREACHED*/
+	return (0);
+}
+
+size_t
+ikev2_encr_block_size(ikev2_xf_encr_t encr)
+{
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL:
+	case IKEV2_ENCR_NULL_AES_GMAC:
+		return (0);
+	case IKEV2_ENCR_DES_IV64:
+	case IKEV2_ENCR_DES:
+	case IKEV2_ENCR_DES_IV32:
+	case IKEV2_ENCR_3DES:
+	case IKEV2_ENCR_RC5:
+	case IKEV2_ENCR_RC4:
+	case IKEV2_ENCR_IDEA:
+	case IKEV2_ENCR_CAST:
+	case IKEV2_ENCR_BLOWFISH:
+	case IKEV2_ENCR_3IDEA:
+		return (8);
+	case IKEV2_ENCR_AES_CBC:
+	case IKEV2_ENCR_AES_CTR:
+	case IKEV2_ENCR_XTS_AES:
+	case IKEV2_ENCR_AES_CCM_8:
+	case IKEV2_ENCR_AES_CCM_12:
+	case IKEV2_ENCR_AES_CCM_16:
+	case IKEV2_ENCR_AES_GCM_8:
+	case IKEV2_ENCR_AES_GCM_12:
+	case IKEV2_ENCR_AES_GCM_16:
+	case IKEV2_ENCR_CAMELLIA_CBC:
+	case IKEV2_ENCR_CAMELLIA_CTR:
+	case IKEV2_ENCR_CAMELLIA_CCM_8:
+	case IKEV2_ENCR_CAMELLIA_CCM_12:
+	case IKEV2_ENCR_CAMELLIA_CCM_16:
+		return (16);
+	}
+	/*NOTREACHED*/
+	return (0);
+}
+
+size_t
+ikev2_encr_iv_size(ikev2_xf_encr_t encr)
+{
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL:
+		return (0);
+	case IKEV2_ENCR_DES_IV32:
+		return (4);
+	case IKEV2_ENCR_DES_IV64:
+		return (8);
+	default:
+		return (ikev2_encr_block_size(encr));
+	}
+}
+
+encr_modes_t
+ikev2_encr_mode(ikev2_xf_encr_t encr)
+{
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL:
+	case IKEV2_ENCR_NULL_AES_GMAC:
+	case IKEV2_ENCR_XTS_AES:
+		return (MODE_NONE);
+	case IKEV2_ENCR_DES_IV64:
+	case IKEV2_ENCR_DES:
+	case IKEV2_ENCR_DES_IV32:
+	case IKEV2_ENCR_3DES:
+	case IKEV2_ENCR_RC5:
+	case IKEV2_ENCR_RC4:
+	case IKEV2_ENCR_IDEA:
+	case IKEV2_ENCR_CAST:
+	case IKEV2_ENCR_BLOWFISH:
+	case IKEV2_ENCR_3IDEA:
+	case IKEV2_ENCR_AES_CBC:
+	case IKEV2_ENCR_CAMELLIA_CBC:
+		return (MODE_CBC);
+	case IKEV2_ENCR_AES_CTR:
+	case IKEV2_ENCR_CAMELLIA_CTR:
+		return (MODE_CTR);
+	case IKEV2_ENCR_AES_CCM_8:
+	case IKEV2_ENCR_AES_CCM_12:
+	case IKEV2_ENCR_AES_CCM_16:
+	case IKEV2_ENCR_CAMELLIA_CCM_8:
+	case IKEV2_ENCR_CAMELLIA_CCM_12:
+	case IKEV2_ENCR_CAMELLIA_CCM_16:
+		return (MODE_CCM);
+	case IKEV2_ENCR_AES_GCM_8:
+	case IKEV2_ENCR_AES_GCM_12:
+	case IKEV2_ENCR_AES_GCM_16:
+		return (MODE_GCM);
+	}
+	/*NOTREACHED*/
+	return (MODE_NONE);
+}
+
+CK_MECHANISM_TYPE
+ikev2_auth_to_p11(ikev2_xf_auth_t auth)
+{
+	switch (auth) {
+	case IKEV2_XF_AUTH_NONE:
+		return (0);
+	case IKEV2_XF_AUTH_HMAC_MD5_96:
+		return (CKM_MD5_HMAC);
+	case IKEV2_XF_AUTH_HMAC_SHA1_96:
+		return (CKM_SHA_1_HMAC);
+	case IKEV2_XF_AUTH_DES_MAC:
+		return (CKM_DES_MAC);
+	case IKEV2_XF_AUTH_KPDK_MD5:
+		return (CKM_MD5_HMAC);	/* XXX: verify */
+	case IKEV2_XF_AUTH_AES_XCBC_96:
+		return (CKM_AES_XCBC_MAC_96);
+	case IKEV2_XF_AUTH_HMAC_MD5_128:
+		return (CKM_MD5_HMAC);
+	case IKEV2_XF_AUTH_HMAC_SHA1_160:
+		return (CKM_SHA_1_HMAC);
+	case IKEV2_XF_AUTH_AES_CMAC_96:
+		return (CKM_AES_CMAC);
+	case IKEV2_XF_AUTH_AES_128_GMAC:
+	case IKEV2_XF_AUTH_AES_192_GMAC:
+	case IKEV2_XF_AUTH_AES_256_GMAC:
+		return (CKM_AES_GMAC);
+	case IKEV2_XF_AUTH_HMAC_SHA2_256_128:
+		return (CKM_SHA256_HMAC);
+	case IKEV2_XF_AUTH_HMAC_SHA2_384_192:
+		return (CKM_SHA384_HMAC);
+	case IKEV2_XF_AUTH_HMAC_SHA2_512_256:
+		return (CKM_SHA512_HMAC);
+	}
+
+	/*NOTREACHED*/
+	return (0);
+}
+
+size_t
+ikev2_auth_icv_size(ikev2_xf_encr_t encr, ikev2_xf_auth_t auth)
+{
+	switch (encr) {
+	case IKEV2_ENCR_NONE:
+	case IKEV2_ENCR_NULL:
+	case IKEV2_ENCR_NULL_AES_GMAC:
+	case IKEV2_ENCR_DES_IV64:
+	case IKEV2_ENCR_DES:
+	case IKEV2_ENCR_DES_IV32:
+	case IKEV2_ENCR_3DES:
+	case IKEV2_ENCR_RC5:
+	case IKEV2_ENCR_RC4:
+	case IKEV2_ENCR_IDEA:
+	case IKEV2_ENCR_CAST:
+	case IKEV2_ENCR_BLOWFISH:
+	case IKEV2_ENCR_3IDEA:
+	case IKEV2_ENCR_AES_CBC:
+	case IKEV2_ENCR_AES_CTR:
+	case IKEV2_ENCR_XTS_AES:
+	case IKEV2_ENCR_CAMELLIA_CBC:
+	case IKEV2_ENCR_CAMELLIA_CTR:
+		break;
+	case IKEV2_ENCR_AES_CCM_8:
+	case IKEV2_ENCR_AES_GCM_8:
+	case IKEV2_ENCR_CAMELLIA_CCM_8:
+		ASSERT3S(auth, ==, IKEV2_XF_AUTH_NONE);
+		return (8);
+	case IKEV2_ENCR_AES_CCM_12:
+	case IKEV2_ENCR_AES_GCM_12:
+	case IKEV2_ENCR_CAMELLIA_CCM_12:
+		ASSERT3S(auth, ==, IKEV2_XF_AUTH_NONE);
+		return (12);
+	case IKEV2_ENCR_AES_CCM_16:
+	case IKEV2_ENCR_AES_GCM_16:
+	case IKEV2_ENCR_CAMELLIA_CCM_16:
+		ASSERT3S(auth, ==, IKEV2_XF_AUTH_NONE);
+		return (16);
+	}
+
+	switch (auth) {
+	case IKEV2_XF_AUTH_NONE:
+		return (0);
+	case IKEV2_XF_AUTH_HMAC_MD5_96:
+	case IKEV2_XF_AUTH_HMAC_SHA1_96:
+	case IKEV2_XF_AUTH_AES_XCBC_96:
+	case IKEV2_XF_AUTH_AES_CMAC_96:
+		return (12);
+	case IKEV2_XF_AUTH_DES_MAC:	/* a guess */
+	case IKEV2_XF_AUTH_KPDK_MD5:
+	case IKEV2_XF_AUTH_HMAC_MD5_128:
+	case IKEV2_XF_AUTH_AES_128_GMAC:
+	case IKEV2_XF_AUTH_HMAC_SHA2_256_128:
+		return (16);
+	case IKEV2_XF_AUTH_HMAC_SHA1_160:
+		return (20);
+	case IKEV2_XF_AUTH_AES_192_GMAC:
+	case IKEV2_XF_AUTH_HMAC_SHA2_384_192:
+		return (24);
+	case IKEV2_XF_AUTH_AES_256_GMAC:
+	case IKEV2_XF_AUTH_HMAC_SHA2_512_256:
+		return (32);
+	}
+	/*NOTREACHED*/
+	return (0);
+}
+
+/*
+ * Destroy a PKCS#11 object with nicer error messages in case of failure.
+ */
+void
+pkcs11_destroy_obj(const char *name, CK_OBJECT_HANDLE_PTR objp,
+    bunyan_logger_t *l)
+{
+	CK_RV ret;
+
+	if (objp == NULL || *objp == CK_INVALID_HANDLE)
+		return;
+
+	if ((ret = C_DestroyObject(p11h, *objp)) != CKR_OK) {
+		PKCS11ERR(error, (l == NULL) ? log : l, "C_DestroyObject", ret);
+	} else {
+		*objp = CK_INVALID_HANDLE;
+	}
+}
+
+/*
+ * Scatter/gather digest calculation.  Upon failure, B_FALSE is returned.
+ */
+boolean_t
+pkcs11_digest(CK_MECHANISM_TYPE alg, const buf_t *restrict in, size_t n,
+    buf_t *restrict out, bunyan_logger_t *l)
+{
+	CK_MECHANISM	mech;
+	CK_RV		ret;
+
+	mech.mechanism = alg;
+	mech.pParameter = NULL_PTR;
+	mech.ulParameterLen = 0;
+
+	if ((ret = C_DigestInit(p11h, &mech)) != CKR_OK) {
+		PKCS11ERR(error, l, "C_DigestInit", ret);
+		return (B_FALSE);
+	}
+
+	for (size_t i = 0; i < n; i++) {
+		ret = C_DigestUpdate(p11h, in[i].b_ptr, buf_left(&in[i]));
+		if (ret != CKR_OK) {
+			PKCS11ERR(error, l, "C_DigestUpdate", ret);
+			return (B_FALSE);
+		}
+	}
+
+	CK_ULONG len = out->b_len;
+
+	ret = C_DigestFinal(p11h, out->b_ptr, &len);
+	if (ret != CKR_OK) {
+		PKCS11ERR(error, l, "C_DigestFinal", ret);
+		return (B_FALSE);
+	}
+
+	if (len > buf_left(out)) {
+		bunyan_error(l, "Output buffer for C_Digest was too small",
+		    BUNYAN_T_STRING, "func", __func__,
+		    BUNYAN_T_STRING, "file", __FILE__,
+		    BUNYAN_T_INT32, "line", __LINE__,
+		    BUNYAN_T_UINT32, "outsz", (uint32_t)buf_left(out),
+		    BUNYAN_T_UINT32, "digestsz", (uint32_t)len,
+		    BUNYAN_T_END);
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+static CK_RV
+pkcs11_callback_handler(CK_SESSION_HANDLE session, CK_NOTIFICATION surrender,
+    void *context)
+{
+	_NOTE(ARGUNUSED(session, context));
+	VERIFY3U(surrender, ==, CKN_SURRENDER);
+
+	return (CKR_OK);
+}
+
+/*
+ * Sadly, string fields in PKCS#11 structs are not NUL-terminated and
+ * are space padded, so this converts it into a more traditional C-string
+ * with quoting so space padding is evident
+ */
+static void
+fmtstr(char *buf, size_t buflen, CK_UTF8CHAR *src, size_t srclen)
+{
+	ASSERT3U(srclen + 1, <=, buflen);
+
+	(void) memset(buf, 0, buflen);
+	(void) memcpy(buf, src, srclen);
+
+	for (char *p = buf + strlen(buf) - 1; p >= buf && *p == ' '; p--)
+		*p = '\0';
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pkcs11.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,83 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.  All rights reserved.
+ * Use is subject to license terms.
+ */
+
+#ifndef _PKCS11_H
+#define	_PKCS11_H
+
+#include <sys/types.h>
+#include <security/cryptoki.h>
+#include <bunyan.h>
+#include "defs.h"
+#include "ikev2.h"
+#include "buf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define	PKCS11ERR(_lvl, _log, _p11f, _rv, ...)				\
+	(void) bunyan_##_lvl((_log), "PKCS#11 call failed",		\
+	BUNYAN_T_STRING, "func", _p11f,					\
+	BUNYAN_T_UINT64, "errnum", (uint64_t)(_rv),			\
+	BUNYAN_T_STRING, "err", pkcs11_strerror(_rv),			\
+	## __VA_ARGS__,							\
+	BUNYAN_T_END)
+
+typedef enum encr_mode {
+	MODE_NONE,
+	MODE_CBC,
+	MODE_CTR,
+	MODE_CCM,
+	MODE_GCM
+} encr_modes_t;
+
+extern CK_INFO pkcs11_info;
+extern CK_SESSION_HANDLE p11h;
+
+/* PKCS#11 functions. */
+void pkcs11_init(void);
+void pkcs11_fini(void);
+
+boolean_t pkcs11_digest(CK_MECHANISM_TYPE, const buf_t *restrict, size_t,
+    buf_t *restrict, bunyan_logger_t *);
+void pkcs11_destroy_obj(const char *, CK_OBJECT_HANDLE_PTR, bunyan_logger_t *);
+
+CK_MECHANISM_TYPE ikev2_encr_to_p11(ikev2_xf_encr_t);
+encr_modes_t ikev2_encr_mode(ikev2_xf_encr_t);
+size_t ikev2_encr_block_size(ikev2_xf_encr_t);
+size_t ikev2_encr_iv_size(ikev2_xf_encr_t);
+
+CK_MECHANISM_TYPE ikev2_auth_to_p11(ikev2_xf_auth_t);
+size_t ikev2_auth_icv_size(ikev2_xf_encr_t, ikev2_xf_auth_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PKCS11_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pkt.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,1020 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+#include <stddef.h>
+#include <assert.h>
+#include <umem.h>
+#include <string.h>
+#include <errno.h>
+#include <sys/types.h>
+#include <sys/byteorder.h>
+#include <ipsec_util.h>
+#include <locale.h>
+#include <netinet/in.h>
+#include <security/cryptoki.h>
+#include <errno.h>
+#include <sys/socket.h>
+#include <pthread.h>
+#include <sys/debug.h>
+#include <note.h>
+#include <err.h>
+#include <limits.h>
+#include "ikev1.h"
+#include "ikev2.h"
+#include "ikev2_sa.h"
+#include "pkt.h"
+#include "pkt_impl.h"
+#include "pkcs11.h"
+
+static umem_cache_t	*pkt_cache;
+
+static size_t pkt_item_rank(pkt_stack_item_t);
+static boolean_t pkt_finish(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+static int pkt_reset(void *);
+
+pkt_t *
+pkt_out_alloc(uint64_t i_spi, uint64_t r_spi, uint8_t version,
+    uint8_t exch_type, uint32_t msgid)
+{
+	pkt_t *pkt = umem_cache_alloc(pkt_cache, UMEM_DEFAULT);
+
+	if (pkt == NULL)
+		return (NULL);
+
+	pkt->pkt_header.initiator_spi = i_spi;
+	pkt->pkt_header.responder_spi = r_spi;
+	pkt->pkt_header.version = version;
+	pkt->pkt_header.exch_type = exch_type;
+	pkt->pkt_header.msgid = msgid;
+
+	/*
+	 * Skip over bytes in pkt->raw for the header -- we keep
+	 * pkt->header (the local byte order copy) updated and then
+	 * write out the final version (in network byte order) in this
+	 * space once we're done building the packet by stacking a
+	 * finish callback before anything else.
+	 */
+	pkt_stack_push(pkt, PSI_PACKET, pkt_finish, 0);
+	pkt->pkt_ptr += sizeof (ike_header_t);
+	return (pkt);
+}
+
+static boolean_t
+pkt_finish(pkt_t *restrict pkt, uchar_t *restrict ptr, uintptr_t swaparg,
+    size_t numpay)
+{
+	NOTE(ARGUNUSED(swaparg, numpay))
+
+	ike_header_t *rawhdr;
+
+	rawhdr = (ike_header_t *)ptr;
+	pkt->pkt_header.length = pkt_len(pkt);
+	pkt_hdr_hton(rawhdr, &pkt->pkt_header);
+	return (B_TRUE);
+}
+
+struct pkt_count_s {
+	pkt_t *pkt;
+	size_t paycount;
+	size_t ncount;
+};
+
+static pkt_walk_ret_t
+pkt_count_cb(uint8_t paytype, uint8_t resv, uchar_t *restrict ptr, size_t len,
+    void *restrict cookie)
+{
+	struct pkt_count_s *data = cookie;
+
+	data->paycount++;
+	if (paytype == IKEV1_PAYLOAD_NOTIFY ||
+	    paytype == IKEV2_PAYLOAD_NOTIFY)
+		data->ncount++;
+	return (PKT_WALK_OK);
+}
+
+boolean_t
+pkt_count_payloads(uchar_t *restrict buf, size_t buflen, uint8_t first,
+    size_t *paycount, size_t *ncount)
+{
+	struct pkt_count_s count = { 0 };
+	if (pkt_payload_walk(buf, buflen, pkt_count_cb, first,
+	    &count) != PKT_WALK_OK)
+		return (B_FALSE);
+
+	if (paycount != NULL)
+		*paycount = count.paycount;
+	if (ncount != NULL)
+		*ncount = count.ncount;
+	return (B_TRUE);
+}
+
+static pkt_walk_ret_t
+pkt_payload_cb(uint8_t paytype, uint8_t resv, uchar_t *restrict ptr, size_t len,
+    void *restrict cookie)
+{
+	struct pkt_count_s *data = cookie;
+	pkt_payload_t *payp = NULL;
+
+	payp = pkt_payload(data->pkt, data->paycount++);
+	payp->pp_ptr = ptr;
+	payp->pp_len = len;
+	payp->pp_type = paytype;
+	return (PKT_WALK_OK);
+}
+
+boolean_t
+pkt_index_payloads(pkt_t *pkt, uchar_t *buf, size_t buflen, uint8_t first,
+    size_t start_idx)
+{
+	struct pkt_count_s data = {
+		.pkt = pkt,
+		.paycount = start_idx
+	};
+
+	ASSERT3P((uchar_t *)&pkt->raw, <, buf);
+	ASSERT3P(pkt->pkt_ptr, >=, buf + buflen);
+
+	if (pkt_payload_walk(buf, buflen, pkt_payload_cb, first,
+	    &data) != PKT_WALK_OK)
+		return (B_FALSE);
+	return (B_TRUE);
+}
+
+/* make sure pkt can hold paycount payloads indexes and ncount notify indexes */
+boolean_t
+pkt_size_index(pkt_t *pkt, size_t paycount, size_t ncount)
+{
+	size_t amt = 0;
+
+	if (paycount > PKT_PAYLOAD_NUM && paycount != pkt->pkt_payload_count) {
+		pkt_payload_t *payp = NULL;
+
+		amt = paycount - PKT_PAYLOAD_NUM;
+		amt *= sizeof (pkt_payload_t);
+		payp = umem_zalloc(amt, UMEM_DEFAULT);
+		if (payp == NULL)
+			return (B_FALSE);
+
+		if (pkt->pkt_payload_extra != NULL) {
+			/* XXX: ASSERT() / VERIFY() on shrink? */
+			if (paycount > pkt->pkt_payload_count) {
+				amt = pkt->pkt_payload_count - PKT_PAYLOAD_NUM; 
+				amt *= sizeof (pkt_payload_t);
+			}
+			(void) memcpy(payp, pkt->pkt_payload_extra, amt);
+
+			amt = pkt->pkt_payload_count - PKT_PAYLOAD_NUM;
+			amt *= sizeof (pkt_payload_t);
+			umem_free(pkt->pkt_payload_extra, amt);
+		}
+		pkt->pkt_payload_extra = payp;
+		pkt->pkt_payload_count = paycount;
+	}
+
+	if (ncount > PKT_NOTIFY_NUM && ncount != pkt->pkt_notify_count) {
+		pkt_notify_t *np;
+
+		amt = ncount - PKT_NOTIFY_NUM;
+		amt *= sizeof (pkt_notify_t);
+		np = umem_zalloc(amt, UMEM_DEFAULT);
+		if (np == NULL)
+			return (B_FALSE);
+
+		if (pkt->pkt_notify_extra != NULL) {
+			/* XXX: ASSERT() / VERIFY() on shrink? */
+			if (ncount > pkt->pkt_notify_count) {
+				amt = pkt->pkt_notify_count - PKT_NOTIFY_NUM;
+				amt *= sizeof (pkt_notify_t);
+			}
+			(void) memcpy(np, pkt->pkt_notify_extra, amt);
+
+			amt = pkt->pkt_notify_count - PKT_NOTIFY_NUM;
+			amt *= sizeof (pkt_notify_t);
+			umem_free(pkt->pkt_notify_extra, amt);
+		}
+		pkt->pkt_notify_extra = np;
+		pkt->pkt_notify_count = ncount;
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * Allocate an pkt_t for an inbound packet, populate the local byte order
+ * header, and cache the location of the payloads in the payload field.
+ */
+pkt_t *
+pkt_in_alloc(uchar_t *buf, size_t buflen)
+{
+	ike_header_t *hdr = (ike_header_t *)buf;
+	pkt_t *pkt = NULL;
+	size_t paycount = 0, ncount = 0;
+	uint8_t first;
+
+	if (buflen > MAX_PACKET_SIZE) {
+		/* XXX: msg */
+		errno = EOVERFLOW;
+		return (NULL);
+	}
+	if (buflen != ntohl(hdr->length)) {
+		/* XXX: msg */
+		return (NULL);
+	}
+
+	first = hdr->next_payload;
+	buf += sizeof (*hdr);
+	buflen -= sizeof (*hdr);
+	if (!pkt_count_payloads(buf, buflen, first, &paycount, &ncount))
+		return (NULL);
+
+	if ((pkt = umem_cache_alloc(pkt_cache, UMEM_DEFAULT)) == NULL) {
+		/* XXX: msg */
+		return (NULL);
+	}
+
+	if (paycount > PKT_PAYLOAD_NUM) {
+		size_t len = paycount - PKT_PAYLOAD_NUM;
+		len *= sizeof (pkt_payload_t);
+		pkt->pkt_payload_extra = umem_zalloc(len, UMEM_DEFAULT);
+		if (pkt->pkt_payload_extra == NULL) {
+			pkt_free(pkt);
+			return (NULL);
+		}
+	}
+
+	if (ncount > PKT_NOTIFY_NUM) {
+		size_t len = ncount - PKT_NOTIFY_NUM;
+		len *= sizeof (pkt_notify_t);
+		pkt->pkt_notify_extra = umem_zalloc(len, UMEM_DEFAULT);
+		if (pkt->pkt_notify_extra == NULL) {
+			pkt_free(pkt);
+			return (NULL);
+		}
+	}
+
+	(void) memcpy(&pkt->pkt_raw, buf, buflen);
+	pkt->pkt_payload_count = paycount;
+	pkt->pkt_notify_count = ncount;
+	pkt_hdr_ntoh(&pkt->pkt_header, (const ike_header_t *)&pkt->pkt_raw);
+	pkt->pkt_ptr += buflen;
+
+	VERIFY(pkt_index_payloads(pkt, pkt_start(pkt), pkt_len(pkt), first, 0));
+	return (pkt);
+}
+
+static boolean_t
+payload_finish(pkt_t *restrict pkt, uchar_t *restrict ptr, uintptr_t arg,
+    size_t numsub)
+{
+	NOTE(ARGUNUSED(numsub))
+	ike_payload_t pay = { 0 };
+	size_t len = (size_t)(pkt->pkt_ptr - ptr);
+
+	ASSERT3P(pkt->pkt_ptr, >, ptr);
+	ASSERT3U(len, <, MAX_PACKET_SIZE);
+
+	(void) memcpy(&pay, ptr, sizeof (pay));
+	pay.pay_next = (uint8_t)arg;
+	pay.pay_length = htons((uint16_t)len);
+	(void) memcpy(ptr, &pay, sizeof (pay));
+	return (B_TRUE);
+}
+
+boolean_t
+pkt_add_payload(pkt_t *pkt, uint8_t ptype, uint8_t resv)
+{
+	ike_payload_t pay = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (pay))
+		return (B_FALSE);
+
+	/* Special case for first payload */
+	if (pkt->pkt_ptr - (uchar_t *)&pkt->pkt_raw == sizeof (ike_header_t))
+		pkt->pkt_header.next_payload = (uint8_t)ptype;
+
+	/*
+	 * Otherwise we'll set it when we replace the current top of
+	 * the stack
+	 */
+	pkt_stack_item_t type =
+	    (ptype == IKEV2_PAYLOAD_SA) ? PSI_SA : PSI_PAYLOAD;
+	pkt_stack_push(pkt, type, payload_finish, (uintptr_t)ptype);
+	pay.pay_next = IKE_PAYLOAD_NONE;
+	pay.pay_reserved = resv;
+	PKT_APPEND_STRUCT(pkt, pay);
+	return (B_TRUE);
+}
+
+static boolean_t prop_finish(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+
+boolean_t
+pkt_add_prop(pkt_t *pkt, uint8_t propnum, uint8_t proto, size_t spilen,
+    uint64_t spi)
+{
+	ike_prop_t	prop = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (prop) + spilen)
+		return (B_FALSE);
+
+	pkt_stack_push(pkt, PSI_PROP, prop_finish, (uintptr_t)IKE_PROP_MORE);
+
+	prop.prop_more = IKE_PROP_NONE;
+	prop.prop_num = propnum;
+	prop.prop_proto = (uint8_t)proto;
+	prop.prop_spilen = spilen;
+	PKT_APPEND_STRUCT(pkt, prop);
+
+	switch (spilen) {
+	case sizeof (uint32_t):
+		ASSERT3U(spi, <, UINT_MAX);
+		put32(pkt, (uint32_t)spi);
+		break;
+	case sizeof (uint64_t):
+		put64(pkt, spi);
+		break;
+	case 0:
+		break;
+	default:
+		INVALID(spilen);
+	}
+
+	return (B_TRUE);
+}
+
+static boolean_t
+prop_finish(pkt_t *restrict pkt, uchar_t *restrict ptr, uintptr_t more,
+    size_t numxform)
+{
+	ike_prop_t	prop = { 0 };
+
+	(void) memcpy(&prop, ptr, sizeof (prop));
+	prop.prop_more = (uint8_t)more;
+	prop.prop_len = htons((uint16_t)(pkt->pkt_ptr - ptr));
+	prop.prop_numxform = (uint8_t)numxform;
+	(void) memcpy(ptr, &prop, sizeof (prop));
+	return (B_TRUE);
+}
+
+static boolean_t pkt_xform_finish(pkt_t *restrict, uchar_t *restrict, uintptr_t,
+    size_t);
+
+boolean_t
+pkt_add_xform(pkt_t *pkt, uint8_t xftype, uint8_t xfid)
+{
+	ike_xform_t	xf = { 0 };
+
+	if (pkt_write_left(pkt) < sizeof (xf))
+		return (B_FALSE);
+
+	pkt_stack_push(pkt, PSI_XFORM, pkt_xform_finish,
+	    (uintptr_t)IKE_XFORM_MORE);
+
+	ASSERT3U(xfid, <, USHRT_MAX);
+
+	/* mostly for completeness */
+	xf.xf_more = IKE_XFORM_NONE;
+	xf.xf_type = xftype;
+	xf.xf_id = htons((uint16_t)xfid);
+	PKT_APPEND_STRUCT(pkt, xf);
+	return (B_TRUE);
+}
+
+static boolean_t
+pkt_xform_finish(pkt_t *restrict pkt, uchar_t *restrict ptr, uintptr_t more,
+    size_t numattr)
+{
+	ike_xform_t	xf = { 0 };
+
+	(void) memcpy(&xf, ptr, sizeof (xf));
+	xf.xf_more = more;
+	xf.xf_len = htons((uint16_t)(pkt->pkt_ptr - ptr));
+	(void) memcpy(ptr, &xf, sizeof (xf));
+	return (B_TRUE);
+}
+
+boolean_t
+pkt_add_xform_attr_tv(pkt_t *pkt, uint16_t type, uint16_t val)
+{
+	ike_xf_attr_t	attr = { 0 };
+
+	ASSERT3U(type, <, 0x8000);
+	ASSERT3U(val, <, 0x10000);
+
+	if (pkt_write_left(pkt) < sizeof (attr))
+		return (B_FALSE);
+
+	pkt_stack_push(pkt, PSI_XFORM_ATTR, NULL, 0);
+	attr.attr_type = htons(IKE_ATTR_TYPE(IKE_ATTR_TV, type));
+	attr.attr_len = htons(val);
+	PKT_APPEND_STRUCT(pkt, attr);
+	return (B_TRUE);
+}
+
+boolean_t
+pkt_add_xform_attr_tlv(pkt_t *pkt, uint16_t type, const uchar_t *attrp,
+    size_t attrlen)
+{
+	ike_xf_attr_t attr = { 0 };
+
+	ASSERT3U(type, <, 0x8000);
+	ASSERT3U(attrlen, <, 0x10000);
+
+	if (pkt_write_left(pkt) < sizeof (attr) + attrlen)
+		return (B_FALSE);
+
+	pkt_stack_push(pkt, PSI_XFORM_ATTR, NULL, 0);
+	attr.attr_type = htons(IKE_ATTR_TYPE(IKE_ATTR_TLV, type));
+	attr.attr_len = htons(attrlen);
+	PKT_APPEND_STRUCT(pkt, attr);
+	(void) memcpy(pkt->pkt_ptr, attrp, attrlen);
+	pkt->pkt_ptr += attrlen;
+	return (B_TRUE);
+}
+
+boolean_t
+pkt_add_cert(pkt_t *restrict pkt, uint8_t encoding, const uchar_t *data,
+    size_t datalen)
+{
+	if (pkt_write_left(pkt) < 1 + datalen)
+		return (B_FALSE);
+
+	*(pkt->pkt_ptr++) = encoding;
+	(void) memcpy(pkt->pkt_ptr, data, datalen);
+	pkt->pkt_ptr += datalen;
+	return (B_TRUE);
+}
+
+void
+pkt_hdr_ntoh(ike_header_t *restrict dest,
+    const ike_header_t *restrict src)
+{
+	ASSERT(IS_P2ALIGNED(dest, sizeof (uint64_t)));
+	ASSERT(IS_P2ALIGNED(src, sizeof (uint64_t)));
+	ASSERT3P(src, !=, dest);
+
+	dest->initiator_spi = ntohll(src->initiator_spi);
+	dest->responder_spi = ntohll(src->responder_spi);
+	dest->msgid = ntohl(src->msgid);
+	dest->length = ntohl(src->length);
+	dest->next_payload = src->next_payload;
+	dest->exch_type = src->exch_type;
+	dest->flags = src->flags;
+	dest->version = src->version;
+}
+
+void
+pkt_hdr_hton(ike_header_t *restrict dest,
+    const ike_header_t *restrict src)
+{
+	ASSERT(IS_P2ALIGNED(dest, sizeof (uint64_t)));
+	ASSERT(IS_P2ALIGNED(src, sizeof (uint64_t)));
+	ASSERT3P(src, !=, dest);
+
+	dest->initiator_spi = htonll(src->initiator_spi);
+	dest->responder_spi = htonll(src->responder_spi);
+	dest->msgid = htonl(src->msgid);
+	dest->length = htonl(src->length);
+	dest->next_payload = src->next_payload;
+	dest->exch_type = src->exch_type;
+	dest->flags = src->flags;
+	dest->version = src->version;
+}
+
+/*
+ * Move all the payloads over amt to insert a payload at the front of the
+ * packet.  Currently used for IKEV2 cookies.
+ */
+boolean_t
+pkt_pay_shift(pkt_t *pkt, uint8_t type, size_t num, ssize_t amt)
+{
+	uchar_t *start = pkt_start(pkt) + sizeof (ike_header_t);
+	pkt_payload_t *pay = NULL;
+
+	if (pkt_write_left(pkt) < amt)
+		return (B_FALSE);
+	if (!pkt_size_index(pkt, pkt->pkt_payload_count + num,
+	    pkt->pkt_notify_count))
+		return (B_FALSE);
+
+	(void) memmove(start + amt, start, (size_t)(pkt->pkt_ptr - start));
+	pkt->pkt_ptr += amt;
+
+	for (uint16_t i = 0; i < pkt->pkt_payload_count; i++) {
+		pay = pkt_payload(pkt, pkt->pkt_payload_count - i);
+		pay[0] = pay[-1];
+		pay[0].pp_ptr += amt;
+	}
+	pay[-1].pp_type = type;
+	pay[-1].pp_len = amt;
+
+	ike_header_t *hdr = (ike_header_t *)&pkt->pkt_raw;
+	ike_payload_t pld = {
+	    .pay_next = pkt->pkt_header.next_payload,
+	    .pay_length = htons((uint16_t)amt)
+	};
+
+	/* Keep the two in sync for sanity's sake */
+	pkt->pkt_header.next_payload = hdr->next_payload = type;
+
+	(void) memcpy(start, &pld, sizeof (pld));
+	pkt->pkt_ptr += amt;
+	return (B_TRUE);
+}
+
+/*
+ * The packet structure of both IKEv1 and IKEv2 consists of a packet with
+ * a header that contains a possibly arbitrary number of payloads.  Certain
+ * payloads can contain an arbitrary number of sub structures.  Some of those
+ * substructures can themselves contain a potentially arbitrary number of
+ * sub-sub structures.
+ *
+ * One of the vexing aspects of the IKE specification is that the design of
+ * these structures makes it cumbersome to know a priori what some of the values
+ * should be until all the embedded structures have been added.  For example
+ * the payload header looks like this (taken from RFC 7296):
+ *
+ *                    1                   2                   3
+ *  0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ * | Next Payload  |C|  RESERVED   |         Payload Length        |
+ * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
+ *
+ * The 'next payload' field (the type of payload in the payload structure
+ * that immediately follows this payload)  cannot be set until it is known
+ * what the * next payload will be (if any).  Similarly, the length of the
+ * current payload cannot be easily calculated until all embedded structures
+ * for this payload are known.  As Bruce Schneier et. al. have pointed out,
+ * this design is overly complicated and does not provide any improvement in
+ * security.  However, we are (sadly) stuck with it.
+ *
+ * To keep the code involved in a given exchange from being buried in
+ * loads of complicated tedium dealing with this, we have created a
+ * hopefully not too clever way to handle setting those fields so
+ * the exhanging handling code rarely needs to worry about it.  It allows
+ * for code similar to:
+ *
+ * ikev2_add_sa(pkt, ...);
+ * ikev2_add_proposal(pkt, IKEV2_PROTO_ESP, ...);
+ * ikev2_add_xform(pkt, IKEV2_XF_ENCR, IKEV2_ENCR_AES_CBC);
+ * ikev2_add_xform_attr(pkt, IKEV2_XF_ATTR_KEYLEN, 256)
+ * ikev2_add_nonce(pkt, ....)
+ * ...
+ * ikev2_send(pkt, ...);
+ *
+ * For any of the IKE structures, generally one or more of the following
+ * questions cannot be answered until subsequent structures are added:
+ *
+ * 	1. Some sort of information about the next structure of the same
+ * 	type (e.g. type of the next payload, if another proposal is present
+ * 	after the current proposal, etc).
+ * 	2. What is the size of this structure (with all embedded structures)?
+ * 	3. How many substructures are present in this structure?
+ *
+ * To be able to answer these questions, the general approach is that
+ * information about the state of the datagram at the time a structure is
+ * appeneded is saved in the pkt_t, and a post-processing function/callback is
+ * invoked after any embedded structures have been added.  This callback
+ * is given the number of embedded structures that have been added, as well as
+ * the position of the start of the structure this callback is invoked for.
+ * This allows the callback to determine both the size of the structure
+ * as well as the number of substructures.  In addition, an argument given
+ * while pushing a subsequent structure of the same type (payload, xform, etc)
+ * is passed to the callback -- this allows the callback to answer question
+ * #1 e.g. when pushing a new payload, it's type is the argument that's
+ * given to the callback invoked for the previous payload.
+ *
+ * To make this all work, each type of structure is assigned a 'rank' for
+ * lack of a better term (suggestions welcome).  Lower ranked structures can
+ * embed compatible higher ranked structures.  Since IKE structures cannot
+ * embed themselves, when we attempt to append a structure of equal or lower
+ * rank to the last structure appended, we know we are done embedding
+ * structures into the previous structure of equal or lower rank. If we are
+ * adding a structure of higher rank than the last structure added, we know we
+ * are embedding a structure.  If it's the same rank, we are pushing a simiar
+ * type of object and should bump the count of objects.
+ */
+
+static size_t
+pkt_item_rank(pkt_stack_item_t type)
+{
+	switch (type) {
+	case PSI_NONE:
+		return (0);
+	case PSI_PACKET:
+		return (1);
+	case PSI_SK:
+		return (2);
+	/*
+	 * same rank, but distinct to allow verification that SA payloads
+	 * can only occur either at the start of a datagram, or as the
+	 * first item inside an SK payload
+	 */
+	case PSI_SA:
+	case PSI_PAYLOAD:
+		return (3);
+	case PSI_DEL:
+	case PSI_TSP:
+	case PSI_PROP:
+		return (4);
+	case PSI_TS:
+	case PSI_XFORM:
+		return (5);
+	case PSI_XFORM_ATTR:
+		return (6);
+	}
+	/*NOTREACHED*/
+	return (SIZE_MAX);
+}
+
+static boolean_t
+pkt_stack_empty(pkt_t *pkt)
+{
+	return (pkt->stksize == 0 ? B_TRUE : B_FALSE);
+}
+
+static pkt_stack_t *
+pkt_stack_top(pkt_t *pkt)
+{
+	if (pkt_stack_empty(pkt))
+		return (NULL);
+	return (&pkt->stack[pkt->stksize - 1]);
+}
+
+static pkt_stack_t *
+pkt_stack_pop(pkt_t *pkt)
+{
+	if (pkt_stack_empty(pkt))
+		return (NULL);
+	return (&pkt->stack[--pkt->stksize]);
+}
+
+static int
+pkt_stack_rank(pkt_t *pkt)
+{
+	pkt_stack_t *stk = pkt_stack_top(pkt);
+	pkt_stack_item_t type = (stk != NULL) ? stk->stk_type : PSI_NONE;
+
+	return (pkt_item_rank(type));
+}
+
+static size_t pkt_stack_unwind(pkt_t *, pkt_stack_item_t, uintptr_t);
+
+/*
+ * Save structure information as we append a new payload
+ * Args:
+ * 	pkt	The packet in question
+ * 	type	The type of structure being added
+ * 	finish	The post-processing callback to run for this structure
+ * 	swaparg	The argument passed to the callback function of the previous
+ * 		post-processing callback for the same type of structure.
+ */
+void
+pkt_stack_push(pkt_t *pkt, pkt_stack_item_t type, pkt_finish_fn finish,
+    uintptr_t swaparg)
+{
+	pkt_stack_t	*stk;
+	size_t		count;
+	pkt_stack_item_t top_type = PSI_NONE;
+
+	if (pkt_stack_top(pkt) != NULL)
+		top_type = pkt_stack_top(pkt)->stk_type;
+
+	/*
+	 * If we're adding stuff in the wrong spot, that's a very egregious
+	 * bug, so die if we do
+	 */
+	switch (type) {
+	case PSI_PACKET:
+		VERIFY3S(top_type, ==, PSI_NONE);
+		break;
+	case PSI_SK:
+		VERIFY3S(top_type, ==, PSI_PACKET);
+		break;
+	case PSI_SA:
+		VERIFY(top_type == PSI_PACKET || top_type == PSI_SK);
+		break;
+	case PSI_PAYLOAD:
+		VERIFY(top_type != PSI_PACKET && top_type != PSI_NONE);
+		break;
+	case PSI_PROP:
+		VERIFY(top_type == PSI_SA || top_type == PSI_PROP ||
+		    top_type == PSI_XFORM || top_type == PSI_XFORM_ATTR);
+		break;
+	case PSI_XFORM:
+		VERIFY(top_type == PSI_XFORM || top_type == PSI_PROP ||
+		    top_type == PSI_XFORM_ATTR);
+		break;
+	case PSI_XFORM_ATTR:
+		VERIFY(top_type == PSI_XFORM || top_type == PSI_XFORM_ATTR);
+		break;
+	case PSI_DEL:
+		VERIFY(top_type == PSI_SA || top_type == PSI_DEL);
+		break;
+	case PSI_TSP:
+		VERIFY3U(top_type, ==, PSI_SA);
+		break;
+	case PSI_TS:
+		VERIFY(top_type == PSI_TSP || top_type == PSI_TS);
+		break;
+	case PSI_NONE:
+		INVALID("type");
+		break;
+	}
+
+	count = pkt_stack_unwind(pkt, type, swaparg);
+
+	ASSERT3U(pkt_stack_rank(pkt), <, pkt_item_rank(type));
+	ASSERT3U(pkt->stksize, <, PKT_STACK_DEPTH);
+
+	stk = &pkt->stack[pkt->stksize++];
+
+	stk->stk_finish = finish;
+	stk->stk_ptr = pkt->pkt_ptr;
+	stk->stk_count = count + 1;
+	stk->stk_type = type;
+}
+
+/*
+ * This is where the magic happens.  Pop off what's saved in pkt->stack
+ * and run all the post processing until the rank of the top item in
+ * the stack is lower than the rank of what we're about to add (contained in
+ * type).  Return the running count of structures of the same rank as type.
+ */
+static size_t
+pkt_stack_unwind(pkt_t *pkt, pkt_stack_item_t type, uintptr_t swaparg)
+{
+	pkt_stack_t	*stk = NULL;
+	size_t		count = 0;
+	size_t		rank = pkt_item_rank(type);
+	size_t		stk_rank = 0;
+
+	while (!pkt_stack_empty(pkt) &&
+	    (stk_rank = pkt_stack_rank(pkt)) >= rank) {
+		stk = pkt_stack_pop(pkt);
+		if (stk->stk_finish != NULL) {
+			boolean_t ret;
+
+			ret = stk->stk_finish(pkt, stk->stk_ptr,
+			    (stk_rank == rank) ? swaparg : 0, count);
+			pkt->pkt_stk_error |= !ret;
+		}
+
+		/*
+		 * This was initialized to 0, and is deliberately set after
+		 * calling the post-processing callback so that the
+		 * post-processing callback called in the next iteration
+		 * of the loop (if it happens) gets the count of embedded
+		 * structures (e.g. proposal post-processing function gets
+		 * the count of embedded transform structures).
+		 */
+		count = stk->stk_count;
+	}
+
+	ASSERT3U(pkt_stack_rank(pkt), <, rank);
+
+	if (stk != NULL && stk_rank == rank)
+		return (stk->stk_count);
+	return (0);
+}
+
+/* pops off all the callbacks in preparation for sending */
+boolean_t
+pkt_done(pkt_t *pkt)
+{
+	(void) pkt_stack_unwind(pkt, PSI_NONE, 0);
+	return (pkt->pkt_stk_error);
+}
+
+/*
+ * Call cb on each encountered payload.
+ * data - the first payload to walk
+ * len - total size of the buffer to walk (should end on payload boundary)
+ * cb - callback function to invoke on each payload
+ * first - payload type of the first payload
+ * cookie - data passed to callback
+ */
+pkt_walk_ret_t
+pkt_payload_walk(uchar_t *restrict data, size_t len, pkt_walk_fn_t cb,
+    uint8_t first, void *restrict cookie)
+{
+	uchar_t			*ptr = data;
+	uint8_t			paytype = first;
+	pkt_walk_ret_t		ret = PKT_WALK_OK;
+
+	/* 0 is used for both IKEv1 and IKEv2 to indicate last payload */
+	while (len > 0 && paytype != 0) {
+		ike_payload_t pay = { 0 };
+
+		if (len < sizeof (pay)) {
+			/* XXX: truncated */
+			return (PKT_WALK_ERROR);
+		}
+
+		(void) memcpy(&pay, ptr, sizeof (pay));
+		ptr += sizeof (pay);
+
+		/* this length includes the size of the header */
+		pay.pay_length = ntohs(pay.pay_length);
+
+		if (pay.pay_length > len) {
+			/* XXX: truncated */
+			return (PKT_WALK_ERROR);
+		}
+
+		if (cb != NULL) {
+			ret = cb(paytype, pay.pay_reserved, ptr, pay.pay_length,
+			    cookie);
+			if (ret != PKT_WALK_OK)
+				break;
+		}
+
+		paytype = pay.pay_next;
+		ptr += pay.pay_length;
+		len -= pay.pay_length;
+	}
+
+	if (ret == PKT_WALK_OK && len > 0) {
+		/* XXX: extra data */
+		return (PKT_WALK_ERROR);
+	}
+
+	return ((ret != PKT_WALK_OK) ? PKT_WALK_ERROR : PKT_WALK_OK);
+}
+
+static size_t
+pay_to_idx(pkt_t *pkt, pkt_payload_t *pay)
+{
+	if (pay == NULL)
+		return (0);
+
+	size_t idx = 0;
+	if (pay >= pkt->pkt_payloads &&
+	    pay < &pkt->pkt_payloads[PKT_PAYLOAD_NUM]) {
+		idx = (size_t)(pay - pkt->pkt_payloads);
+		VERIFY3U(idx, <, pkt->pkt_payload_count);
+		return (idx);
+	}
+
+	VERIFY3P(pay, >=, pkt->pkt_payload_extra);
+	VERIFY3P(pay, <, pkt->pkt_payload_extra + pkt->pkt_payload_count -
+	    PKT_PAYLOAD_NUM);
+	idx = (size_t)(pay - pkt->pkt_payload_extra);
+	return (idx);
+}
+
+pkt_payload_t *
+pkt_get_payload(pkt_t *pkt, int type, pkt_payload_t *start)
+{
+	size_t idx = pay_to_idx(pkt, start);
+
+	VERIFY3S(type, >=, 0);
+	VERIFY3S(type, <, 0xff);
+
+	if (start != NULL)
+		idx++;
+
+	for (size_t i = idx; i < pkt->pkt_payload_count; i++) {
+		pkt_payload_t *pay = pkt_payload(pkt, i);
+
+		if (pay->pp_type == (uint8_t)type)
+			return (pay);
+	}
+	return (NULL);
+}
+
+static size_t
+notify_to_idx(pkt_t *pkt, pkt_notify_t *n)
+{
+	if (n == NULL)
+		return (0);
+
+	size_t idx = 0;
+
+	if (n >= pkt->pkt_notify &&
+	    n < &pkt->pkt_notify[PKT_NOTIFY_NUM]) {
+		idx = (size_t)(n - pkt->pkt_notify);
+		VERIFY3U(idx, <, pkt->pkt_notify_count);
+		return (idx);
+	}
+
+	VERIFY3P(n, >=, pkt->pkt_notify_extra);
+	VERIFY3P(n, <, pkt->pkt_notify_extra + pkt->pkt_notify_count -
+	    PKT_NOTIFY_NUM);
+
+	idx = (size_t)(n - pkt->pkt_notify_extra);
+	return (idx);
+}
+
+pkt_notify_t *
+pkt_get_notify(pkt_t *pkt, int type, pkt_notify_t *start)
+{
+	size_t idx = notify_to_idx(pkt, start);
+
+	VERIFY3S(type, >=, 0);
+	VERIFY3S(type, <=, USHRT_MAX);
+
+	if (start != NULL)
+		idx++;
+
+	for (size_t i = idx; i < pkt->pkt_notify_count; i++) {
+		pkt_notify_t *n = pkt_notify(pkt, i);
+
+		if (n->pn_type == (uint16_t)type)
+			return (n);
+	}
+	return (NULL);
+}
+
+static int
+pkt_ctor(void *buf, void *ignore, int flags)
+{
+	_NOTE(ARGUNUSUED(ignore, flags))
+
+	pkt_t *pkt = buf;
+	(void) memset(pkt, 0, sizeof (pkt_t));
+	pkt->pkt_ptr = pkt_start(pkt);
+	return (0);
+}
+
+void
+pkt_free(pkt_t *pkt)
+{
+	if (pkt == NULL)
+		return;
+
+	size_t len = 0;
+	if (pkt->pkt_payload_extra != NULL) {
+		len = pkt->pkt_payload_count - PKT_PAYLOAD_NUM;
+		len *= sizeof (pkt_payload_t);
+		umem_free(pkt->pkt_payload_extra, len);
+	}
+
+	if (pkt->pkt_notify_extra != NULL) {
+		len = pkt->pkt_notify_count - PKT_NOTIFY_NUM;
+		len *= sizeof (pkt_notify_t);
+		umem_free(pkt->pkt_notify_extra, len);
+	}
+
+	pkt_ctor(pkt, NULL, 0);
+	umem_cache_free(pkt_cache, pkt);
+}
+
+void
+pkt_init(void)
+{
+	pkt_cache = umem_cache_create("pkt cache", sizeof (pkt_t),
+	    sizeof (uint64_t), pkt_ctor, NULL, NULL, NULL, NULL, 0);
+	if (pkt_cache == NULL)
+		err(EXIT_FAILURE, "Unable to create pkt umem cache");
+}
+
+void
+pkt_fini(void)
+{
+	umem_cache_destroy(pkt_cache);
+}
+
+void
+put32(pkt_t *pkt, uint32_t val)
+{
+	ASSERT3U(pkt_write_left(pkt), >=, sizeof (uint32_t));
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 24) & (uint32_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 16) & (uint32_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 8) & (uint32_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)(val & (uint32_t)0xff);
+}
+
+void
+put64(pkt_t *pkt, uint64_t val)
+{
+	ASSERT3U(pkt_write_left(pkt), >=, sizeof (uint64_t));
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 56) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 48) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 40) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 32) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 24) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 16) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)((val >> 8) & (uint64_t)0xff);
+	*(pkt->pkt_ptr++) = (uchar_t)(val & (uint64_t)0xff);
+}
+
+extern uchar_t *pkt_start(pkt_t *);
+extern size_t pkt_len(const pkt_t *);
+extern size_t pkt_write_left(const pkt_t *);
+extern size_t pkt_read_left(const pkt_t *, const uchar_t *);
+extern pkt_payload_t *pkt_payload(pkt_t *, uint16_t);
+extern pkt_notify_t *pkt_notify(pkt_t *, uint16_t);
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pkt.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,201 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyirght 2017 Joyent, Inc.
+ */
+
+#ifndef _PKT_H
+#define	_PKT_H
+
+#include <sys/types.h>
+#include "ike.h"
+#include "ikev2.h"
+#include "buf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct ikev2_sa;
+struct pkt;
+struct pkt_stack;
+
+typedef struct pkt		pkt_t;
+typedef struct pkt_stack	pkt_stack_t;
+
+typedef boolean_t (*pkt_finish_fn)(struct pkt *restrict, uchar_t *restrict,
+    uintptr_t, size_t);
+
+typedef enum pkt_stack_item {
+	PSI_NONE,
+	PSI_PACKET,
+	PSI_SK,
+	PSI_SA,
+	PSI_PAYLOAD,
+	PSI_PROP,
+	PSI_XFORM,
+	PSI_XFORM_ATTR,
+	PSI_DEL,
+	PSI_TSP,
+	PSI_TS
+} pkt_stack_item_t;
+
+struct pkt_stack {
+	pkt_finish_fn		stk_finish;
+	uchar_t			*stk_ptr;
+	size_t			stk_count;
+	pkt_stack_item_t	stk_type;
+};
+#define	PKT_STACK_DEPTH	(6)	/* maximum depth needed */
+
+typedef struct pkt_payload {
+	uchar_t		*pp_ptr;
+	uint16_t	pp_len;
+	uint8_t		pp_type;
+} pkt_payload_t;
+#define	PKT_PAYLOAD_NUM	(16)	/* usually don't need more than this */
+
+typedef struct pkt_notify {
+	uchar_t		*pn_ptr;
+	uint16_t	pn_type;
+	uint16_t	pn_len;
+} pkt_notify_t;
+#define	PKT_NOTIFY_NUM	(8)
+
+#define	MAX_PACKET_SIZE	(8192)	/* largest datagram we accept */
+struct pkt {
+				/* NOT refheld */
+	struct ikev2_sa		*pkt_sa;
+
+				/* Raw packet data */
+	uint64_t		pkt_raw[SADB_8TO64(MAX_PACKET_SIZE)];
+				/*
+				 * Points to one past last bit of valid data
+				 * in pkt_raw
+				 */
+	uchar_t			*pkt_ptr;
+
+				/* Copy of ISAKMP header in local byte order */
+	ike_header_t		pkt_header;
+
+	pkt_payload_t		pkt_payloads[PKT_PAYLOAD_NUM];
+	pkt_payload_t		*pkt_payload_extra;
+	uint16_t		pkt_payload_count;
+
+	pkt_notify_t		pkt_notify[PKT_NOTIFY_NUM];
+	pkt_notify_t		*pkt_notify_extra;
+	uint16_t		pkt_notify_count;
+
+	struct pkt_stack	stack[PKT_STACK_DEPTH];
+	uint_t			stksize;
+	boolean_t		pkt_stk_error;
+
+	size_t			pkt_xmit;
+};
+
+inline uchar_t *
+pkt_start(pkt_t *pkt)
+{
+	return ((uchar_t *)&pkt->pkt_raw);
+}
+
+inline size_t
+pkt_len(const pkt_t *pkt)
+{
+	const uchar_t *start = (const uchar_t *)&pkt->pkt_raw;
+	size_t len = (size_t)(pkt->pkt_ptr - start);
+
+	ASSERT3P(pkt->pkt_ptr, >=, start);
+	ASSERT3U(len, <=, MAX_PACKET_SIZE);
+	return ((size_t)(pkt->pkt_ptr - start));
+}
+
+inline size_t
+pkt_write_left(const pkt_t *pkt)
+{
+	return (MAX_PACKET_SIZE - pkt_len(pkt));
+}
+
+inline size_t
+pkt_read_left(const pkt_t *pkt, const uchar_t *ptr)
+{
+	const uchar_t *start = (const uchar_t *)&pkt->pkt_raw;
+	ASSERT3P(ptr, >=, start);
+	ASSERT3P(ptr, <=, pkt->pkt_ptr);
+	return ((size_t)(pkt->pkt_ptr - ptr));
+}
+
+inline pkt_payload_t *
+pkt_payload(pkt_t *pkt, uint16_t idx)
+{
+	ASSERT3U(idx, <, pkt->pkt_payload_count);
+	if (idx < PKT_PAYLOAD_NUM)
+		return (&pkt->pkt_payloads[idx]);
+	return (pkt->pkt_payload_extra + (idx - PKT_PAYLOAD_NUM));
+}
+
+inline pkt_notify_t *
+pkt_notify(pkt_t *pkt, uint16_t idx)
+{
+	ASSERT3U(idx, <, pkt->pkt_notify_count);
+	if (idx < PKT_NOTIFY_NUM)
+		return (&pkt->pkt_notify[idx]);
+	return (pkt->pkt_notify_extra + (idx - PKT_NOTIFY_NUM));
+}
+
+#define	PKT_APPEND_STRUCT(_pkt, _struct) do {				\
+	ASSERT3U(pkt_write_left(_pkt), <=, sizeof (_struct));		\
+	(void) memcpy((_pkt)->pkt_ptr, &(_struct), sizeof (_struct));	\
+	(_pkt)->pkt_ptr += sizeof (_struct);				\
+/*CONSTCOND*/								\
+} while (0)
+
+#define	PKT_APPEND_DATA(_pkt, _ptr, _len) do {		\
+	ASSERT3U(pkt_write_left(_pkt), <=, len);	\
+	(void) memcpy((_pkt)->pkt_ptr, (_ptr), (_len));	\
+	(_pkt)->pkt_ptr += (_len);			\
+/*CONSTCOND*/						\
+} while (0)
+
+void pkt_hdr_ntoh(ike_header_t *restrict, const ike_header_t *restrict);
+void pkt_hdr_hton(ike_header_t *restrict, const ike_header_t *restrict);
+void put32(pkt_t *, uint32_t);
+void put64(pkt_t *, uint64_t);
+
+boolean_t pkt_done(pkt_t *);
+void pkt_init(void);
+void pkt_fini(void);
+void pkt_free(pkt_t *);
+
+boolean_t pkt_pay_shift(pkt_t *, uint8_t, size_t, ssize_t);
+pkt_payload_t *pkt_get_payload(pkt_t *, int, pkt_payload_t *);
+pkt_notify_t *pkt_get_notify(pkt_t *, int, pkt_notify_t *);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PKT_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/pkt_impl.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,62 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.  All rights reserved.
+ */
+
+#ifndef _PKT_IMPL_H
+#define	_PKT_IMPL_H
+
+#include "pkt.h"
+#include "ike.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum pkt_walk_ret {
+	PKT_WALK_ERROR	= -1,
+	PKT_WALK_OK = 0,
+	PKT_WALK_STOP = 1
+} pkt_walk_ret_t;
+
+typedef pkt_walk_ret_t (*pkt_walk_fn_t)(uint8_t, uint8_t, uchar_t *restrict,
+    size_t, void *restrict);
+pkt_walk_ret_t pkt_payload_walk(uchar_t *restrict, size_t, pkt_walk_fn_t,
+    uint8_t, void *restrict);
+
+boolean_t pkt_count_payloads(uchar_t *restrict, size_t, uint8_t, size_t *,
+    size_t *);
+boolean_t pkt_index_payloads(pkt_t *, uchar_t *, size_t, uint8_t, size_t);
+boolean_t pkt_size_index(pkt_t *, size_t, size_t);
+
+pkt_t *pkt_in_alloc(uchar_t *, size_t);
+pkt_t *pkt_out_alloc(uint64_t, uint64_t, uint8_t, uint8_t, uint32_t);
+void pkt_free(pkt_t *);
+
+void pkt_stack_push(pkt_t *restrict, pkt_stack_item_t, pkt_finish_fn,
+    uintptr_t);
+
+boolean_t pkt_add_payload(pkt_t *, uint8_t, uint8_t);
+boolean_t pkt_add_prop(pkt_t *, uint8_t, uint8_t, size_t, uint64_t);
+boolean_t pkt_add_xform(pkt_t *, uint8_t, uint8_t);
+boolean_t pkt_add_xform_attr_tv(pkt_t *, uint16_t, uint16_t);
+boolean_t pkt_add_xform_attr_tlv(pkt_t *restrict, uint16_t,
+    const uchar_t *restrict, size_t);
+boolean_t pkt_add_cert(pkt_t *restrict, uint8_t, const uchar_t *restrict,
+    size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PKT_IMPL_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/preshared.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,2041 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <strings.h>
+#include <errno.h>
+#include <ctype.h>
+#include <libintl.h>
+#include <arpa/inet.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <net/pfkeyv2.h>
+#include <pthread.h>
+#include <synch.h>
+#include <assert.h>
+#include <bunyan.h>
+#include <err.h>
+
+#include "preshared.h"
+#include "defs.h"
+#include <ipsec_util.h>
+
+static FILE *fp;
+
+/* Preshared key lock */
+static pthread_rwlock_t ps_rwlock = PTHREAD_RWLOCK_INITIALIZER;
+
+#define	hd2num(hd) (((hd) >= '0' && (hd) <= '9') ? ((hd) - '0') : \
+	(((hd) >= 'a' && (hd) <= 'f') ? ((hd) - 'a' + 10) : ((hd) - 'A' + 10)))
+
+#define	ADDRBITS_V4	32	/* number of bits in IPv4 address */
+#define	ADDRBITS_V6	128	/* number of bits in IPv6 address */
+
+
+#define	CHR_CURLY_BRACE_OPEN		'{'
+#define	CHR_CURLY_BRACE_CLOSE		'}'
+#define	CHR_COMMENT_BEGIN		'#'
+#define	CHR_COLON			':'
+#define	CHR_SLASH			'/'
+#define	CHR_BACKSLASH			'\\'
+#define	CHR_DOUBLEQUOTE			'"'
+#define	CHR_SPACE			' '
+#define	CHR_NEWLINE			'\n'
+#define	CHR_TAB				'\t'
+#define	CHR_NULL			'\0'
+
+/*
+ * Error message strings
+ */
+
+static const char *err_arr [] = {
+#define	ERR_LOCID_MISMATCH		0
+	"Local Id value does not match the specified Localid type",
+#define	ERR_REMID_MISMATCH		1
+	"Remote Id does not match the remoteid type",
+#define	ERR_MAINMODE_LOCID_NOTIP	2
+	"Local Id can only be an IP address",
+#define	ERR_MAINMODE_REMID_NOTIP	3
+	"Remote Id can only be an IP address",
+#define	ERR_ENTRY_NOBEGINBRACE		4
+	"Syntax error - Entry does not start with an open brace",
+#define	ERR_INVALID_FIELDNAME		5
+	"Invalid attribute field name",
+#define	ERR_REPEATED_FIELD		6
+	"Field in an entry is repeated",
+#define	ERR_INVALID_LOCALID_TYPE	7
+	"Invalid Local Id type",
+#define	ERR_MISSING_LOCALID		8
+	"localid not defined",
+#define	ERR_MISSING_REMID		9
+	"remotedid not defined",
+#define	ERR_INVALID_REMID_TYPE		10
+	"Invalid Remote Id type",
+#define	ERR_INVALID_IKE_MODE		11
+	"Invalid Ike Mode",
+#define	ERR_INVALID_BIT_SPECIFIER	12
+	"Invalid Bit Specifier",
+#define	ERR_BITLEN2BIG			13
+	"Bit length too large",
+#define	ERR_WARN_LOWBITS_TRUNC		14
+	"Lower bits will be truncated",
+#define	ERR_STRING_NOT_HEX		15
+	"Invalid string format - hex string expected",
+#define	ERR_BADADDR_PREFIXLEN_PART	16
+	"Invalid prefix length format in address",
+#define	ERR_BADADDR_SLASH_UNEXPECTED	17
+	"Unxpected '/' in address string",
+#define	ERR_BADADDR_TRY_AGAIN		18
+	"Bad address string - try again later",
+#define	ERR_BADADDR_ADDRESS		19
+	"Bad address string",
+#define	ERR_BADADDR_MISMATCH		20
+	"Address versions do not match",
+#define	ERR_BADADDR_MCAST    21
+	"Multicast address not allowed",
+#define	ERR_BADADDR_4MAPPED    22
+	"V4 mapped in v6 address not allowed",
+#define	ERR_BADADDR_4COMPAT    23
+	"V4 compatible in v6 address not allowed",
+#define	ERR_INVALID_ASCII_STRING	24
+	"Syntax error in ASCII string or quotes",
+};
+
+
+/*
+ * Buffer to hold input line
+ */
+static char	linebuf[1024];
+
+
+/*
+ * Types of tokens
+ */
+#define	PS_TOK_BEGIN_CURLY_BRACE	1
+#define	PS_TOK_END_CURLY_BRACE		2
+#define	PS_TOK_FLD_TYPE			3
+#define	PS_TOK_FLD_VALUE		4
+
+
+/*
+ * The prototype syntax of this file used, e.g. "localid" and "localidtype".
+ * The aliases support transitioning to using same keywords as the ike/config
+ * parser, e.g. "local_id" and "local_id_type".
+ *
+ * Also, {local,remote}_addr are aliases for {local,remote}_id for now,
+ * eventually they should be handled differently.
+ */
+static const keywdtab_t fldstab[] = {
+	{ PS_FLD_LOCID,		"localid" },
+	{ PS_FLD_LOCID,		"local_id" },
+	{ PS_FLD_LOCID,		"local_addr" },
+	{ PS_FLD_LOCID_TYPE,	"localidtype" },
+	{ PS_FLD_LOCID_TYPE,	"local_id_type" },
+	{ PS_FLD_REMID,		"remoteid" },
+	{ PS_FLD_REMID,		"remote_id" },
+	{ PS_FLD_REMID,		"remote_addr" },
+	{ PS_FLD_REMID_TYPE,	"remoteidtype" },
+	{ PS_FLD_REMID_TYPE,	"remote_id_type" },
+	{ PS_FLD_IKE_MODE,	"ike_mode" },
+	{ PS_FLD_IKE_MODE,	"mode" },
+	{ PS_FLD_KEY,		"key" },
+};
+
+static const keywdtab_t idstab[] = {
+	{ PS_ID_IP,			"IP" },
+	{ PS_ID_IP4,			"IPv4" },
+	{ PS_ID_IP6,			"IPv6" },
+	{ PS_ID_IP,			"IP_SUBNET" },
+	{ PS_ID_IP4,			"IPv4_SUBNET" },
+	{ PS_ID_IP6,			"IPv6_SUBNET" },
+};
+
+static const keywdtab_t ikmstab[] = {
+	{ PS_IKM_MAIN,			"main" },
+};
+
+/*
+ * Head and tail of linked list of entry data structures
+ */
+static preshared_entry_t *ps_head, *ps_tail;
+
+/*
+ * Global counters for use in reporting approximate error location
+ * in config file
+ */
+static int err_line_number;
+static int err_entry_number;
+
+/*
+ * Function prototypes
+ */
+static int getidtype(char *);
+static int getfldtype(char *);
+static int getikmtype(char *);
+static int postprocess_entry(preshared_entry_t *, char **);
+static char *get_next_token(char **);
+static char *readnextline(FILE *);
+static preshared_entry_t *getnextentry(FILE *, char **);
+static uint8_t *parsekey(char *, uint_t *, uint_t *, char **);
+/* Note: in_get{prefilen,addr}, in6_getaddr stolen from ifconfig.c */
+static int in_getprefixlen(char *, boolean_t, int);
+static int in_getaddr(char *, struct sockaddr_storage *, int *, char **);
+static int in6_getaddr(char *, struct sockaddr_storage *, int *, char **);
+static boolean_t check_if_v6(char *);
+
+
+/*
+ * Functions
+ */
+
+/*
+ * Check for same preshared entry.
+ * Caller holds ps_rwlock as reader
+ */
+static boolean_t
+same_psent(preshared_entry_t *ptr, preshared_entry_t *ps)
+{
+	struct sockaddr_in	*ptr_v4, *ps_v4;
+	struct sockaddr_in6	*ptr_v6, *ps_v6;
+
+	assert(RW_LOCK_HELD(&ps_rwlock));
+
+	/*
+	 * Check for duplicate entries.  Preshared type
+	 * of IPv4/IPv6 has a NULL locid/remid string and
+	 * has the sockaddr_storages structures populated.
+	 * All others store themselves in the aforementioned
+	 * strings.
+	 */
+
+	/* Local and Remote ID types must match */
+	if ((ptr->pe_locidtype != ps->pe_locidtype) ||
+	    (ptr->pe_remidtype != ps->pe_remidtype))
+		return (B_FALSE);
+
+	/* Check local ids */
+	switch (ptr->pe_locidtype) {
+	case PS_ID_IP:
+	case PS_ID_IP4:
+	case PS_ID_SUBNET4:
+		ptr_v4 = (struct sockaddr_in *)&ptr->pe_locid_sa;
+		ps_v4 = (struct sockaddr_in *)&ps->pe_locid_sa;
+		if ((uint32_t)ptr_v4->sin_addr.s_addr !=
+		    (uint32_t)ps_v4->sin_addr.s_addr)
+			return (B_FALSE);
+		/*
+		 * Prefix length either same number or same error
+		 * PS_PLEN_NO_PREFIX for non subnet
+		 */
+		if (&ptr->pe_locid_plen != &ps->pe_locid_plen)
+			return (B_FALSE);
+		break;
+	case PS_ID_IP6:
+	case PS_ID_SUBNET6:
+		ptr_v6 = (struct sockaddr_in6 *)&ptr->pe_locid_sa;
+		ps_v6 = (struct sockaddr_in6 *)&ps->pe_locid_sa;
+		if (!(IN6_ARE_ADDR_EQUAL(&ptr_v6->sin6_addr,
+		    &ps_v6->sin6_addr)))
+			return (B_FALSE);
+		/*
+		 * Prefix length either same number or same error
+		 * PS_PLEN_NO_PREFIX for non subnet
+		 */
+		if (&ptr->pe_locid_plen != &ps->pe_locid_plen)
+			return (B_FALSE);
+		break;
+	default:
+		if (strcmp(ptr->pe_locid, ps->pe_locid) != 0)
+				return (B_FALSE);
+	}
+
+	/* Check remote ids */
+	switch (ptr->pe_remidtype) {
+	case PS_ID_IP:
+	case PS_ID_IP4:
+		ptr_v4 = (struct sockaddr_in *)&ptr->pe_remid_sa;
+		ps_v4 = (struct sockaddr_in *)&ps->pe_remid_sa;
+		if ((uint32_t)ptr_v4->sin_addr.s_addr !=
+		    (uint32_t)ps_v4->sin_addr.s_addr)
+			return (B_FALSE);
+		/*
+		 * Prefix length either same number or same error
+		 * PS_PLEN_NO_PREFIX for non subnet
+		 */
+		if (&ptr->pe_remid_plen != &ps->pe_remid_plen)
+			return (B_FALSE);
+		break;
+	case PS_ID_IP6:
+		ptr_v6 = (struct sockaddr_in6 *)&ptr->pe_remid_sa;
+		ps_v6 = (struct sockaddr_in6 *)&ps->pe_remid_sa;
+		if (!(IN6_ARE_ADDR_EQUAL(&ptr_v6->sin6_addr,
+		    &ps_v6->sin6_addr)))
+			return (B_FALSE);
+		/*
+		 * Prefix length either same number or same error
+		 * PS_PLEN_NO_PREFIX for non subnet
+		 */
+		if (&ptr->pe_remid_plen != &ps->pe_remid_plen)
+			return (B_FALSE);
+		break;
+	default:
+		if (strcmp(ptr->pe_remid, ps->pe_remid) != 0)
+				return (B_FALSE);
+	}
+	return (B_TRUE);
+}
+
+/*
+ * Check for duplicate preshared entry.
+ * Caller holds ps_rwlock as reader
+ */
+static boolean_t
+has_dup(preshared_entry_t *ps, preshared_entry_t **head)
+{
+	preshared_entry_t *ptr;
+
+	assert(RW_LOCK_HELD(&ps_rwlock));
+
+	if (ps == NULL) /* no op */
+		return (B_FALSE);
+
+	ptr = *head;
+
+	while (ptr != NULL) {
+		if (same_psent(ptr, ps))
+			return (B_TRUE);
+		ptr = ptr->pe_next;
+	}
+	return (B_FALSE);
+}
+
+/*
+ * Append entries to list.
+ * Caller holds ps_rwlock as writer
+ */
+static boolean_t
+append_to_list(preshared_entry_t *ps, preshared_entry_t **head,
+    preshared_entry_t **tail)
+{
+	assert(RW_WRITE_HELD(&ps_rwlock));
+
+	if (ps == NULL)		/* no op */
+		return (B_TRUE);
+	assert(ps->pe_refcnt > 0);
+	if (has_dup(ps, head)) {
+		(void) fprintf(stderr,
+		    gettext("Ignoring duplicate preshared entry.\n"));
+		PE_REFRELE(ps);
+		return (B_FALSE);
+	}
+	ps->pe_next = NULL;	/* will be last entry */
+	if (*head == NULL) {
+		/* list initialization  */
+		*head = *tail = ps;
+	} else {
+		(*tail)->pe_next = ps;
+		*tail = (*tail)->pe_next;
+	}
+	return (B_TRUE);
+}
+
+/*
+ * Appends preshared entry to list
+ * Holds ps_rwlock as writer
+ */
+boolean_t
+append_preshared_entry(preshared_entry_t *ps)
+{
+	boolean_t rc;
+
+	(void) pthread_rwlock_wrlock(&ps_rwlock);
+	rc = append_to_list(ps, &ps_head, &ps_tail);
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (rc);
+}
+
+/*
+ * Frees preshared list
+ * Caller holds ps_rwlock as writer
+ */
+static void
+free_preshared_list(preshared_entry_t **head, preshared_entry_t **tail)
+{
+	preshared_entry_t	*ps;
+
+	assert(RW_WRITE_HELD(&ps_rwlock));
+
+	while (*head != NULL) {
+		/* Zero the old list in case of reloading. */
+		ps = *head;
+		if (ps == *tail)
+			*tail = NULL;
+		*head = ps->pe_next;
+		PE_REFRELE(ps);
+	}
+}
+
+static char *
+getidstr(int val)
+{
+	const keywdtab_t *idt;
+
+	for (idt = idstab; idt < A_END(idstab); idt++) {
+		if (val == idt->kw_tag)
+			return (idt->kw_str);
+	}
+	return (NULL);		/* not found */
+}
+
+static int
+getidtype(char *valp)
+{
+	const keywdtab_t *idt;
+
+	for (idt = idstab; idt < A_END(idstab); idt++) {
+		if (strcasecmp(valp, idt->kw_str) == 0)
+			return (idt->kw_tag);
+	}
+	return (-1);		/* not found */
+}
+
+static int
+getfldtype(char *valp)
+{
+	const keywdtab_t *fldt;
+
+	for (fldt = fldstab; fldt < A_END(fldstab); fldt++) {
+		if (strcasecmp(valp, fldt->kw_str) == 0)
+			return (fldt->kw_tag);
+	}
+	return (-1);		/* not found */
+}
+
+static char *
+getikmstr(int val)
+{
+	const keywdtab_t *idt;
+
+	for (idt = ikmstab; idt < A_END(ikmstab); idt++) {
+		if (val == idt->kw_tag)
+			return (idt->kw_str);
+	}
+	return (NULL);		/* not found */
+}
+
+static int
+getikmtype(char *valp)
+{
+	const keywdtab_t *ikmt;
+
+	for (ikmt = ikmstab; ikmt < A_END(ikmstab); ikmt++) {
+		if (strcasecmp(valp, ikmt->kw_str) == 0)
+			return (ikmt->kw_tag);
+	}
+	return (-1);		/* not found */
+
+}
+
+/*
+ * unlike the other get*str functions, this one mallocs the returned
+ * string, so the caller will need to free it.
+ */
+char *
+getkeystr(uint8_t *key, uint_t bytes, uint_t bits)
+{
+	uint8_t	*sp;
+	char	*dp, *buf;
+	uint_t	len;
+
+	/* assume two digits per byte, and no more than 4 digits of bitlen */
+	len = (bytes < 1) + 6;
+	if ((buf = malloc(len)) == NULL)
+		return (NULL);
+
+	sp = key;
+	dp = buf;
+	while (bytes-- != 0) {
+		(void) sprintf(dp, "%02x", *sp++);
+		dp += 2;
+	}
+	(void) sprintf(dp, "/%d", bits);
+
+	return (buf);
+}
+
+/*
+ * Parsing for hex key values.
+ * Return value:
+ *	Pointer to allocated buffer containing the key
+ *	Parameter, "len" contains length of key buffer on successful return
+ *	Parameter, "lbits" contains length of key in bits in the key buffer.
+ * Note:Stolen from ipseckey.c and then adapted for use here.
+ */
+static uint8_t *
+parsehexkey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp)
+{
+	uint8_t *keyp, *keybufp;
+	uint_t i, hexlen = 0, bits, alloclen;
+
+	for (i = 0; input[i] != CHR_NULL && input[i] != CHR_SLASH; i++)
+		hexlen++;
+
+	if (input[i] == CHR_NULL) {
+		bits = 0;
+	} else {
+		/* Have /nn. */
+		input[i] = CHR_NULL;
+		if (sscanf((input + i + 1), "%u", &bits) != 1) {
+			*errp = (char *)err_arr[ERR_INVALID_BIT_SPECIFIER];
+			return (NULL);
+		}
+
+		/* hexlen in nibbles */
+		if (((bits + 3) >> 2) > hexlen) {
+			*errp = (char *)err_arr[ERR_BITLEN2BIG];
+			return (NULL);
+		}
+
+		/*
+		 * Adjust hexlen down if user gave us too small of a bit
+		 * count.
+		 */
+		if ((hexlen << 2) > bits + 3) {
+			/*
+			 * NOTE: Callers don't necessarily handle warnings
+			 * a successful return.
+			 */
+			*errp = (char *)err_arr[ERR_WARN_LOWBITS_TRUNC];
+			hexlen = (bits + 3) >> 2;
+			input[hexlen] = CHR_NULL;
+		}
+	}
+
+	/*
+	 * Allocate.  Remember, hexlen is in nibbles.
+	 */
+
+	alloclen = (hexlen/2 + (hexlen & 0x1));
+	keyp = malloc(alloclen);
+
+	if (keyp == NULL) {
+		*errp = strerror(errno);
+		return (NULL);
+	}
+
+	keybufp = keyp;
+	*keybuflen = alloclen;
+	if (bits == 0)
+		*lbits = (hexlen + (hexlen & 0x1)) << 2;
+	else
+		*lbits = bits;
+
+	/*
+	 * Read in nibbles.  Read in odd-numbered as shifted high.
+	 * (e.g. 123 becomes 0x1230).
+	 */
+
+	for (i = 0; input[i] != CHR_NULL; i += 2) {
+		boolean_t second = (input[i + 1] != CHR_NULL);
+
+		if (!isxdigit(input[i]) ||
+		    (!isxdigit(input[i + 1]) && second)) {
+			free(keybufp); /* free allocated memory on error */
+			*errp = (char *)err_arr[ERR_STRING_NOT_HEX];
+			return (NULL);
+		}
+		*keyp = (hd2num(input[i]) << 4);
+		if (second)
+			*keyp |= hd2num(input[i + 1]);
+		else
+			break;	/* out of for loop. */
+		keyp++;
+	}
+
+	/* bzero the remaining bits if we're a non-octet amount. */
+	if (bits & 0x7)
+		*((input[i] == CHR_NULL) ? keyp - 1 : keyp) &=
+		    0xff << (8 - (bits & 0x7));
+	return (keybufp);
+}
+/*
+ * Parsing for ASCII key values.
+ * Return value:
+ *	Pointer to allocated buffer containing the key
+ *	Parameter, "len" contains length of key buffer on successful return
+ *	Parameter, "lbits" contains length of key in bits in the key buffer.
+ * Note:Stolen from ipseckey.c and then adapted for use here.
+ */
+static uint8_t *
+parseasciikey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp)
+{
+	uint8_t *keyp, *keybufp;
+	uint_t i, asciilen = 0;
+
+	/* Make sure the first and last characters are '"', unescaped */
+
+	if (input[0] != CHR_DOUBLEQUOTE)
+		goto invalid_ascii_string;
+
+	/* Set pointer past first quote, then find the end */
+	input++;
+
+	for (i = 0; input[i] != CHR_NULL; i++)
+		asciilen++;
+	/*
+	 * Length without trailing \0 and leading quote lopped off
+	 * from before must be at least 3 for a well formed key
+	 */
+	if (--asciilen < 3)
+		goto invalid_ascii_string;
+	/*
+	 * Make sure the last quote exists and is not escaped,
+	 * but also watch for valid "this string\\" syntax.
+	 */
+	if (input[asciilen] != CHR_DOUBLEQUOTE)
+		goto invalid_ascii_string;
+
+	/* We know we can dereference these because of length check above */
+	if ((input[asciilen - 1] == CHR_BACKSLASH) &&
+	    (input[asciilen - 2] != CHR_BACKSLASH))
+		goto invalid_ascii_string;
+
+	input[asciilen] = CHR_NULL;
+
+	/* Now we have something well-formed in quotes and length set */
+
+	/*
+	 * Allocate.  If we have backslashes, we can shrink later
+	 */
+
+	keyp = malloc(asciilen);
+	if (keyp == NULL) {
+		*errp = strerror(errno);
+		return (NULL);
+	}
+
+	keybufp = keyp;
+	/*
+	 * Read in bytes, but be aware of next byte for escape purposes
+	 */
+
+	for (i = 0; input[i] != CHR_NULL; i++) {
+		boolean_t second = (input[i + 1] != CHR_NULL);
+
+		if (!isascii(input[i]) ||
+		    (second && !isascii(input[i + 1]))) {
+			goto free_key_buffer;
+		}
+		/*
+		 * Consider backslash an escape character for
+		 * itself, single and double quotes, but not
+		 * for anything else
+		 */
+		if (second && (input[i] == CHR_BACKSLASH)) {
+			if (input[i + 1] == CHR_DOUBLEQUOTE ||
+			    input[i + 1] == CHR_BACKSLASH) {
+				*keyp = input[i + 1];
+				i++;
+				keyp++;
+				asciilen--;
+				continue; /* for loop */
+			}
+		}
+		*keyp = input[i];
+		keyp++;
+	}
+	/* Shrink to fit */
+	*keybuflen = asciilen;
+	*lbits = asciilen << 3;
+	keybufp = realloc(keybufp, asciilen);
+
+	return (keybufp);
+
+free_key_buffer:
+	free(keybufp);
+invalid_ascii_string:
+	*errp = (char *)err_arr[ERR_INVALID_ASCII_STRING];
+	return (NULL);
+}
+
+static uint8_t *
+parsekey(char *input, uint_t *keybuflen, uint_t *lbits, char **errp)
+{
+	/* Determine if this is an ASCII key or hex key */
+	if (input[0] == CHR_DOUBLEQUOTE) {
+		return (parseasciikey(input, keybuflen, lbits, errp));
+	} else {
+		return (parsehexkey(input, keybuflen, lbits, errp));
+	}
+}
+
+static boolean_t
+check_if_v6(char *abuf)
+{
+	char *cp;
+	for (cp = abuf; *cp != CHR_NULL; cp++) {
+		if (*cp == CHR_COLON)
+			return (B_TRUE);
+	}
+	return (B_FALSE);
+}
+
+static boolean_t
+valid_ip6_address(struct sockaddr_in6 *addr, char **errmsgp)
+{
+
+		/* is it mcast */
+		if (IN6_IS_ADDR_MULTICAST(&addr->sin6_addr)) {
+			*errmsgp = (char *)err_arr[ERR_BADADDR_MCAST];
+			return (B_FALSE);
+		}
+		/* is it v4 mapped */
+		if (IN6_IS_ADDR_V4MAPPED(&addr->sin6_addr)) {
+			*errmsgp = (char *)err_arr[ERR_BADADDR_4MAPPED];
+			return (B_FALSE);
+		}
+		/* is it v4 compat */
+		if (IN6_IS_ADDR_V4COMPAT(&addr->sin6_addr)) {
+			*errmsgp = (char *)err_arr[ERR_BADADDR_4COMPAT];
+			return (B_FALSE);
+		}
+
+		return (B_TRUE);
+}
+
+/*
+ * Post process preshared key entry.
+ * Caller holds ps_rwlock as reader
+ */
+static int
+postprocess_entry(preshared_entry_t *ps, char **errmsgp)
+{
+	assert(RW_LOCK_HELD(&ps_rwlock));
+
+	/* ID types default to IP. */
+	if ((ps->pe_flds_mask & PS_FLD_LOCID_TYPE) == 0) {
+		ps->pe_flds_mask |= PS_FLD_LOCID_TYPE;
+		ps->pe_locidtype = PS_ID_IP;
+	}
+	if ((ps->pe_flds_mask & PS_FLD_REMID_TYPE) == 0) {
+		ps->pe_flds_mask |= PS_FLD_REMID_TYPE;
+		ps->pe_remidtype = PS_ID_IP;
+	}
+
+	/*
+	 * Verify that all mandatory fields are there.
+	 * mandatory:  local_id and remote_id (or local_addr and remote_addr),
+	 * mode, and key.
+	 */
+
+	/*
+	 * Note: verify that semantic relationships among fields is fine
+	 * Since we only support main mode, we force the mode to be main.
+	 */
+
+	ps->pe_ike_mode = PS_IKM_MAIN;
+	if ((ps->pe_locidtype != PS_ID_IP) &&
+	    (ps->pe_locidtype != PS_ID_IP4) &&
+	    (ps->pe_locidtype != PS_ID_IP6)) {
+		*errmsgp = (char *)err_arr[ERR_MAINMODE_LOCID_NOTIP];
+		return (-1);
+	}
+	if ((ps->pe_remidtype != PS_ID_IP) &&
+	    (ps->pe_remidtype != PS_ID_IP4) &&
+	    (ps->pe_remidtype != PS_ID_IP6)) {
+		*errmsgp = (char *)err_arr[ERR_MAINMODE_REMID_NOTIP];
+		return (-1);
+	}
+
+	/*
+	 * Parse "id" values now and verify that they match the
+	 * "idtype" associated with them.
+	 * NOTE: Real work is TBD
+	 */
+	if (ps->pe_flds_mask & PS_FLD_LOCID_TYPE) {
+		int retval;
+
+		if (ps->pe_locid == NULL) {
+			*errmsgp = (char *)err_arr[ERR_MISSING_LOCALID];
+			return (-1);
+		}
+
+		switch (ps->pe_locidtype) {
+		case PS_ID_IP:
+		{
+			boolean_t isv6 = check_if_v6(ps->pe_locid);
+			if (isv6) {
+				ps->pe_locidtype = PS_ID_IP6;
+				retval = in6_getaddr(ps->pe_locid,
+				    &ps->pe_locid_sa, &ps->pe_locid_plen,
+				    errmsgp);
+			} else {
+				ps->pe_locidtype = PS_ID_IP4;
+				retval = in_getaddr(ps->pe_locid,
+				    &ps->pe_locid_sa, &ps->pe_locid_plen,
+				    errmsgp);
+			}
+			if (retval < 0)
+				return (-1);
+		}
+		break;
+		case PS_ID_IP4:
+		case PS_ID_SUBNET4:
+			retval = in_getaddr(ps->pe_locid, &ps->pe_locid_sa,
+			    &ps->pe_locid_plen, errmsgp);
+			if (retval < 0)
+				return (-1);
+			break;
+		case PS_ID_IP6:
+		case PS_ID_SUBNET6:
+			retval = in6_getaddr(ps->pe_locid, &ps->pe_locid_sa,
+			    &ps->pe_locid_plen, errmsgp);
+			if (retval < 0)
+				return (-1);
+			break;
+		case PS_ID_SUBNET:
+		{
+			boolean_t isv6 = check_if_v6(ps->pe_locid);
+			if (isv6) {
+				ps->pe_locidtype = PS_ID_SUBNET6;
+				retval = in6_getaddr(ps->pe_locid,
+				    &ps->pe_locid_sa, &ps->pe_locid_plen,
+				    errmsgp);
+			} else {
+				ps->pe_locidtype = PS_ID_SUBNET4;
+				retval = in_getaddr(ps->pe_locid,
+				    &ps->pe_locid_sa, &ps->pe_locid_plen,
+				    errmsgp);
+			}
+			if (retval < 0)
+				return (-1);
+		}
+		break;
+		case PS_ID_RANGE:
+			break;
+		case PS_ID_RANGE4:
+			break;
+		case PS_ID_RANGE6:
+			break;
+		case PS_ID_ASN1DN:
+			break;
+		case PS_ID_ASN1GN:
+			break;
+		case PS_ID_KEYID:
+			break;
+		case PS_ID_FQDN:
+			break;
+		case PS_ID_USER_FQDN:
+			break;
+		}
+	}
+	if (ps->pe_flds_mask & PS_FLD_REMID_TYPE) {
+		int retval;
+
+		if (ps->pe_remid == NULL) {
+			*errmsgp = (char *)err_arr[ERR_MISSING_REMID];
+			return (-1);
+		}
+		switch (ps->pe_remidtype) {
+		case PS_ID_IP:
+		{
+			boolean_t isv6 = check_if_v6(ps->pe_remid);
+			if (isv6) {
+				ps->pe_remidtype = PS_ID_IP6;
+				retval = in6_getaddr(ps->pe_remid,
+				    &ps->pe_remid_sa, &ps->pe_remid_plen,
+				    errmsgp);
+			} else {
+				ps->pe_remidtype = PS_ID_IP4;
+				retval = in_getaddr(ps->pe_remid,
+				    &ps->pe_remid_sa, &ps->pe_remid_plen,
+				    errmsgp);
+			}
+			if (retval < 0)
+				return (-1);
+		}
+		break;
+		case PS_ID_IP4:
+		case PS_ID_SUBNET4:
+			retval = in_getaddr(ps->pe_remid, &ps->pe_remid_sa,
+			    &ps->pe_remid_plen, errmsgp);
+			if (retval < 0)
+				return (-1);
+			break;
+		case PS_ID_IP6:
+		case PS_ID_SUBNET6:
+			retval = in6_getaddr(ps->pe_remid, &ps->pe_remid_sa,
+			    &ps->pe_remid_plen, errmsgp);
+			if (retval < 0)
+				return (-1);
+			break;
+		case PS_ID_SUBNET:
+		{
+			boolean_t isv6 = check_if_v6(ps->pe_remid);
+			if (isv6) {
+				ps->pe_remidtype = PS_ID_SUBNET6;
+				retval = in6_getaddr(ps->pe_remid,
+				    &ps->pe_remid_sa, &ps->pe_remid_plen,
+				    errmsgp);
+			} else {
+				ps->pe_remidtype = PS_ID_SUBNET4;
+				retval = in_getaddr(ps->pe_remid,
+				    &ps->pe_remid_sa, &ps->pe_remid_plen,
+				    errmsgp);
+			}
+			if (retval < 0)
+				return (-1);
+		}
+		break;
+		case PS_ID_RANGE:
+			break;
+		case PS_ID_RANGE4:
+			break;
+		case PS_ID_RANGE6:
+			break;
+		case PS_ID_ASN1DN:
+			break;
+		case PS_ID_ASN1GN:
+			break;
+		case PS_ID_KEYID:
+			break;
+		case PS_ID_FQDN:
+			break;
+		case PS_ID_USER_FQDN:
+			break;
+		}
+	}
+
+	/*
+	 * Reality check address families
+	 * Remote and local address families must match
+	 */
+	if (check_if_v6(ps->pe_locid) != check_if_v6(ps->pe_remid)) {
+		*errmsgp = (char *)err_arr[ERR_BADADDR_MISMATCH];
+		return (-1);
+	}
+
+	/* v6 */
+	if (ps->pe_remidtype == PS_ID_IP6 ||
+	    ps->pe_remidtype == PS_ID_SUBNET6) {
+		struct sockaddr_in6 *local =
+		    (struct sockaddr_in6 *)(&ps->pe_locid_sa);
+		struct sockaddr_in6 *remote =
+		    (struct sockaddr_in6 *)(&ps->pe_remid_sa);
+
+		if (!valid_ip6_address(local, errmsgp) ||
+		    !valid_ip6_address(remote, errmsgp)) {
+			return (-1);
+		}
+	} else {
+		/* v4 */
+		struct sockaddr_in *local =
+		    (struct sockaddr_in *)(&ps->pe_locid_sa);
+		struct sockaddr_in *remote =
+		    (struct sockaddr_in *)(&ps->pe_remid_sa);
+
+		/* are either mcast */
+		if (IN_MULTICAST(ntohl(local->sin_addr.s_addr)) ||
+		    IN_MULTICAST(ntohl(remote->sin_addr.s_addr))) {
+			*errmsgp = (char *)err_arr[ERR_BADADDR_MCAST];
+			return (-1);
+		}
+	}
+
+	return (0);		/* OK return */
+}
+
+/*
+ * Note: Code stolen from ifconfig.c and adapted.
+ *
+ * If "slash" is zero this parses the whole string as
+ * an integer. With "slash" non zero it parses the tail part as an integer.
+ *
+ * If it is not a valid integer this returns BAD_ADDR.
+ * If there is /<n> present this returns PS_PLEN_NO_PREFIX.
+ */
+static int
+in_getprefixlen(char *addr, boolean_t slash, int max_plen)
+{
+	int prefixlen;
+	char *str, *end;
+
+	if (slash) {
+		str = strchr(addr, CHR_SLASH);
+		if (str == NULL)
+			return (PS_PLEN_NO_PREFIX);
+		str++;
+	} else
+		str = addr;
+
+	prefixlen = strtol(str, &end, 10);
+	if (prefixlen < 0)
+		return (PS_PLEN_BAD_ADDR);
+	if (str == end)
+		return (PS_PLEN_BAD_ADDR);
+	if (max_plen != 0 && max_plen < prefixlen)
+		return (PS_PLEN_BAD_ADDR);
+	return (prefixlen);
+}
+
+
+/*
+ * Note: code stolen from ifconfig.c and adapted
+ *
+ * If the last argument is non-NULL allow a <addr>/<n> syntax and
+ * pass out <n> in *plenp.
+ * If <n> doesn't parse return PS_PLEN_BAD_ADDR as *plenp.
+ * If no /<n> is present return PS_PLEN_NO_PREFIX as *plenp.
+ */
+static int
+in_getaddr(char *s, struct sockaddr_storage *saddr, int *plenp, char **errp)
+{
+	struct sockaddr_in *sin = (struct sockaddr_in *)saddr;
+	struct hostent *hp;
+	struct netent *np;
+	char str[BUFSIZ];
+	int error_num;
+
+	(void) strncpy(str, s, sizeof (str));
+
+	/*
+	 * Look for '/'<n> (CHR_SLASH)is plenp
+	 */
+	if (plenp != NULL) {
+		char *cp;
+
+		*plenp = in_getprefixlen(str, B_TRUE, ADDRBITS_V4);
+		if (*plenp == PS_PLEN_BAD_ADDR) {
+			*errp = (char *)err_arr[ERR_BADADDR_PREFIXLEN_PART];
+			return (-1);
+		}
+		cp = strchr(str, CHR_SLASH);
+		if (cp != NULL)
+			*cp = CHR_NULL;
+	} else if (strchr(str, CHR_SLASH) != NULL) {
+		*errp = (char *)err_arr[ERR_BADADDR_SLASH_UNEXPECTED];
+		return (-1);
+	}
+
+	(void) memset(sin, 0, sizeof (*sin));
+
+	/*
+	 *	Try to catch attempts to set the broadcast address to all 1's.
+	 */
+	if (strcmp(str, "255.255.255.255") == 0 ||
+	    (strtoul(str, (char **)NULL, 0) == 0xffffffffUL)) {
+		sin->sin_family = AF_INET;
+		sin->sin_addr.s_addr = 0xffffffff;
+		return (0);
+	}
+
+	hp = getipnodebyname(str, AF_INET, 0, &error_num);
+	if (hp) {
+		sin->sin_family = hp->h_addrtype;
+		(void) memcpy(&sin->sin_addr, hp->h_addr, hp->h_length);
+		freehostent(hp);
+		return (0);
+	}
+	np = getnetbyname(str);
+	if (np) {
+		sin->sin_family = np->n_addrtype;
+		sin->sin_addr = inet_makeaddr(np->n_net, INADDR_ANY);
+		return (0);
+	}
+	if (error_num == TRY_AGAIN) {
+		*errp = (char *)err_arr[ERR_BADADDR_TRY_AGAIN];
+	} else {
+		*errp = (char *)err_arr[ERR_BADADDR_ADDRESS];
+	}
+	return (-1);
+}
+
+/*
+ * Note: Code stolen from ifconfig.c and adapted.
+ *
+ * If the third argument is non-NULL allow a <addr>/<n> syntax and
+ * pass out <n> in *plenp.
+ * If <n> doesn't parse return PS_PLEN_BAD_ADDR as *plenp.
+ * If no /<n> is present return PS_PLEN_NO_PREFIX as *plenp.
+ */
+static int
+in6_getaddr(char *s, struct sockaddr_storage *saddr, int *plenp, char **errp)
+{
+	struct sockaddr_in6 *sin6 = (struct sockaddr_in6 *)saddr;
+	struct hostent *hp;
+	char str[BUFSIZ];
+	int error_num;
+
+	(void) strncpy(str, s, sizeof (str));
+
+	/*
+	 * Look for '/'<n> (CHR_SLASH) is plenp
+	 */
+	if (plenp != NULL) {
+		char *cp;
+
+		*plenp = in_getprefixlen(str, B_TRUE, ADDRBITS_V6);
+		if (*plenp == PS_PLEN_BAD_ADDR) {
+			*errp = (char *)err_arr[ERR_BADADDR_PREFIXLEN_PART];
+			return (-1);
+		}
+		cp = strchr(str, CHR_SLASH);
+		if (cp != NULL)
+			*cp = CHR_NULL;
+	} else if (strchr(str, CHR_SLASH) != NULL) {
+		*errp = (char *)err_arr[ERR_BADADDR_SLASH_UNEXPECTED];
+		return (-1);
+	}
+
+	(void) memset(sin6, 0, sizeof (*sin6));
+
+	hp = getipnodebyname(str, AF_INET6, 0, &error_num);
+	if (hp) {
+		sin6->sin6_family = hp->h_addrtype;
+		(void) memcpy(&sin6->sin6_addr, hp->h_addr, hp->h_length);
+		freehostent(hp);
+		return (0);
+	}
+	if (error_num == TRY_AGAIN) {
+		*errp = (char *)err_arr[ERR_BADADDR_TRY_AGAIN];
+	} else {
+		*errp = (char *)err_arr[ERR_BADADDR_ADDRESS];
+	}
+	return (-1);
+}
+
+/*
+ * char *get_next_token(cpp)
+ *	cpp - on input should point within a null terminated buffer.
+ *	return value - on output will point to past spaces to first
+ *		non-space character that starts a token
+ *	cpp - on output will point past non-space token string pointed
+ *		by return value.
+ *	Side-effects: modifies a potential space character after token
+ *		to null to form  a null terminated token unless a
+ *		double-quote has been seen in the token
+ */
+static char *
+get_next_token(char **cpp)
+{
+	char *cp = *cpp;
+	char *startcp, *endcp;
+	boolean_t quote_seen = B_FALSE;
+
+	/* skip leading whitespaces */
+	while (*cp == CHR_SPACE || *cp == CHR_TAB || *cp == CHR_NEWLINE)
+		cp++;
+
+	if (*cp == CHR_NULL) {	/* end of string */
+		*cpp = NULL;
+		return (NULL);
+	}
+
+	startcp = endcp = cp;
+	while (*endcp != CHR_NULL &&
+	    ((*endcp != CHR_SPACE && *endcp != CHR_TAB) || quote_seen) &&
+	    *endcp != CHR_NEWLINE) {
+		/*
+		 * Within quotes, ignore spaces and tabs as delimiters
+		 * and take note of start and end quotes, ignoring
+		 * backslash escaped characters.
+		 */
+		if (*endcp == CHR_DOUBLEQUOTE &&
+		    ((endcp == startcp) ||
+		    ((endcp > startcp) && *(endcp - 1) != CHR_BACKSLASH)))
+			quote_seen = !quote_seen;
+		endcp++;
+	}
+	if (*endcp != CHR_NULL)
+		*endcp++ = CHR_NULL;
+	*cpp = endcp;
+	return (startcp);
+}
+
+static char *
+readnextline(FILE *fp)
+{
+	char *cp;
+
+	while ((cp = fgets(linebuf, sizeof (linebuf), fp)) != NULL) {
+		err_line_number++; /* NOTE approx - if long lines */
+		if (*cp == CHR_NEWLINE || *cp == CHR_COMMENT_BEGIN)
+			continue;
+		break;
+	}
+	if (cp == NULL)
+		return (NULL);
+	return (linebuf);
+}
+
+
+/*
+ * static preshared_entry_t *getnextentry(fp, errmpp)
+ *	fp
+ *		file pointer to open config file.
+ *	errmpp
+ *		pointer to pointer to buffer containing error message
+ *	return value
+ *		NULL when EOF is reached
+ *		pointer to valid entry otherwise
+ *
+ *	Function scans open input file to get the next entry read from
+ *	the file and instantiated in the preshared_entry_t data structure.
+ *	In case of invalid syntax entries the function exits and does
+ *	not return and process exits.
+ *
+ *	Caller holds ps_rwlock as reader.
+ */
+static preshared_entry_t *
+getnextentry(FILE *fp, char **errmpp)
+{
+	preshared_entry_t *ps;
+	char *cp;		/* pointer to scan input */
+	char *tp;
+	char *errmsg = NULL;
+	int cur_fld_type = -1;	/* "uninitialized" */
+	int entry_done = FALSE;
+	int next_token_expected = PS_TOK_BEGIN_CURLY_BRACE;
+
+	assert(RW_LOCK_HELD(&ps_rwlock));
+
+	*errmpp = NULL;
+
+	ps = calloc(1, sizeof (preshared_entry_t));	/* Auto-zeroes... */
+	if (ps == NULL) {
+		errmsg = strerror(errno);
+		goto fatal_error;
+	}
+	ps->pe_refcnt = 1;
+nextline:
+	/* get next line  */
+
+	/* skip comment and null lines */
+	while (((cp = readnextline(fp)) != NULL) &&
+	    (*cp == CHR_COMMENT_BEGIN || *cp == CHR_NULL))
+		;
+
+	if (cp == NULL) {
+		/* EOF  */
+		PE_REFRELE(ps);
+		return (NULL);
+	}
+
+	for (;;) {
+		if (entry_done == TRUE)
+			break;
+		tp = get_next_token(&cp);
+		if (tp == NULL)
+			goto nextline;
+process_newtoken:
+		/* skip rest of line if comment token */
+		if (*tp == CHR_COMMENT_BEGIN)
+			goto nextline;
+
+		switch (next_token_expected) {
+
+		case PS_TOK_BEGIN_CURLY_BRACE:
+			if (*tp != CHR_CURLY_BRACE_OPEN) {
+				errmsg = (char *)err_arr[
+				    ERR_ENTRY_NOBEGINBRACE];
+				goto bad_syntax;
+			}
+			err_entry_number++;
+			next_token_expected = PS_TOK_FLD_TYPE;
+
+			/*
+			 * Note: We skip tokens assuming whitespace
+			 * termination but relax that for this token.
+			 *
+			 * Reset scan pointer past this token (one character)
+			 * so pointer does not miss any tokens that may
+			 * be glued to this one with no whitespaces.
+			 */
+			tp++;	/* past brace */
+			if (*tp == CHR_NULL) {
+				/* past whitespace terminated brace */
+				tp++;
+			}
+			cp = tp;
+			break;
+
+		case PS_TOK_END_CURLY_BRACE:
+			/*
+			 * Do sanity checks on entry and other
+			 * post-processing
+			 */
+			if (postprocess_entry(ps, &errmsg) < 0)
+				goto bad_semantics;
+			entry_done = TRUE;
+			break;
+
+		case PS_TOK_FLD_TYPE:
+			/*
+			 * Note: could be end-of-entry too...
+			 * here we assume the closing brace is
+			 * preceded by a whitespace. We gave some latitude to
+			 * opening brace, but not to the closing brace
+			 */
+			if (*tp == CHR_CURLY_BRACE_CLOSE) {
+				/*
+				 * End of fields, reset expected token,
+				 * Also reset scan token so this
+				 */
+				next_token_expected = PS_TOK_END_CURLY_BRACE;
+				goto process_newtoken;
+			}
+
+			/* initialize cur_fld_type */
+			if ((cur_fld_type = getfldtype(tp)) < 0) {
+				errmsg = (char *)err_arr[ERR_INVALID_FIELDNAME];
+				goto bad_syntax;
+			}
+
+			if ((ps->pe_flds_mask & cur_fld_type) != 0) {
+				errmsg = (char *)err_arr[ERR_REPEATED_FIELD];
+				goto bad_syntax;
+			}
+
+			ps->pe_flds_mask |= cur_fld_type;
+
+			next_token_expected = PS_TOK_FLD_VALUE;
+			break;
+
+		case PS_TOK_FLD_VALUE:
+
+			/*
+			 * Note: we assume all value fields do
+			 * not embed any whitespace even for complicated
+			 * value syntax such as for "range". Is that a
+			 * "reasonable" assumption ?
+			 *
+			 * Note++: All things being strdup()'d below need
+			 * to be parsed and put into context dependent data
+			 * structures at some point (after entry is read or
+			 * right here).
+			 */
+			switch (cur_fld_type) {
+			case PS_FLD_LOCID_TYPE:
+				if ((ps->pe_locidtype = getidtype(tp)) < 0) {
+					errmsg = (char *)err_arr[
+					    ERR_INVALID_LOCALID_TYPE];
+					goto bad_syntax;
+				}
+				break;
+			case PS_FLD_LOCID:
+				ps->pe_locid = strdup(tp);
+				if (ps->pe_locid == NULL) {
+					errmsg = strerror(errno);
+					goto fatal_error;
+				}
+				break;
+			case PS_FLD_REMID_TYPE:
+				if ((ps->pe_remidtype = getidtype(tp)) < 0) {
+					errmsg = (char *)err_arr[
+					    ERR_INVALID_REMID_TYPE];
+					goto bad_syntax;
+				}
+				break;
+			case PS_FLD_REMID:
+				ps->pe_remid = strdup(tp);
+				if (ps->pe_remid == NULL) {
+					errmsg = strerror(errno);
+					goto fatal_error;
+				}
+				break;
+			case PS_FLD_IKE_MODE:
+				if ((ps->pe_ike_mode = getikmtype(tp)) < 0) {
+					errmsg = (char *)err_arr[
+					    ERR_INVALID_IKE_MODE];
+					goto bad_syntax;
+				}
+				break;
+			case PS_FLD_KEY:
+				ps->pe_keybuf = parsekey(tp,
+				    &ps->pe_keybuf_bytes,
+				    &ps->pe_keybuf_lbits, &errmsg);
+				if (ps->pe_keybuf == NULL)
+					goto fatal_error;
+				else if (errmsg != NULL) {
+					/*
+					 * Utter warning on otherwise okay
+					 * input.
+					 */
+					(void) fprintf(stderr,
+					    gettext("read_preshared: %s\n"),
+					    errmsg);
+				}
+				break;
+			default:
+				/* Should never happen - assert ? */
+				errmsg = (char *)err_arr[
+				    ERR_INVALID_FIELDNAME];
+				goto fatal_error;
+			}
+
+			next_token_expected = PS_TOK_FLD_TYPE;
+			break;
+		}
+	}
+	return (ps);
+	/*
+	 * TBD any differentiation in these errors ?
+	 */
+bad_semantics:
+bad_syntax:
+fatal_error:
+	if (ps != NULL)
+		PE_REFRELE(ps);
+	*errmpp = errmsg;
+	return (NULL);
+}
+
+
+/*
+ * Load any preshared keys.
+ * Boolean parameter 'replace' determines whether the newly-read entries
+ * will replace the existing list, or be appended to it.
+ * On success, returns a linked list of preshared entries, and sets the
+ * error message pointer *errmp to NULL.
+ * On failure, returns NULL and sets *errmp to an error message string.
+ *
+ * Holds ps_rwlock as writer.
+ */
+char *
+preshared_load(const char *ps_filename, int ps_fd, boolean_t replace)
+{
+	preshared_entry_t *ps, *tmp_head = NULL, *tmp_tail = NULL;
+	char *errmp;
+	boolean_t dupfound = B_FALSE;
+
+	(void) pthread_rwlock_wrlock(&ps_rwlock);
+
+	/* TODO more paranoia/permissions checks on file needed */
+
+	if (ps_filename == NULL)
+		fp = fdopen(ps_fd, "r");
+	else
+		fp = fopen(ps_filename, "r");
+	if (fp == NULL) {
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		if (errno == ENOENT)
+			return (NULL);	/* no file == empty file */
+		return (strerror(errno));
+	}
+
+	while ((ps = getnextentry(fp, &errmp)) != NULL) {
+		/* process the new entry */
+
+		/*
+		 * For now, create a list of just the new entries
+		 */
+		if (!replace) {
+			if (has_dup(ps, &ps_head)) {
+				/* Skip duplicates in master list */
+				PE_REFRELE(ps);
+				dupfound = B_TRUE;
+				continue;
+			}
+		}
+		assert(ps->pe_refcnt >= 1);
+		if (!append_to_list(ps, &tmp_head, &tmp_tail)) {
+			/* Duplicate found within new list */
+			dupfound = B_TRUE;
+		}
+	}
+
+	(void) fclose(fp);
+
+	if (errmp == NULL) {
+		/*
+		 * we read the list in successfully; either
+		 * replace or append to global list now.
+		 */
+		if (replace || ps_tail == NULL) {
+			free_preshared_list(&ps_head, &ps_tail);
+			ps_head = tmp_head;
+			ps_tail = tmp_tail;
+		} else {
+			ps_tail->pe_next = tmp_head;
+			ps_tail = tmp_tail;
+		}
+	} else {
+		/*
+		 * had problems reading new list; free it
+		 */
+		free_preshared_list(&tmp_head, &tmp_tail);
+	}
+
+	if (dupfound && (errmp == NULL)) {
+		/* Hack to key off of later, not user visible */
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		return ("DUP");
+	}
+
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (errmp);
+}
+
+/*
+ * Returns 0 if requested entry not found; 1 if deleted successfully.
+ * Holds ps_rwlock as writer.
+ */
+int
+delete_ps(preshared_entry_t *delp)
+{
+	preshared_entry_t	*curp, *prevp;
+
+	(void) pthread_rwlock_wrlock(&ps_rwlock);
+	if (ps_head == NULL) {
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		return (0);
+	}
+
+	if (delp == ps_head) {
+		/* CORNER CASE: Deletion of last entry */
+		if (ps_head == ps_tail)
+			ps_tail = NULL;
+		ps_head = delp->pe_next;
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		PE_REFRELE(delp);
+		return (1);
+	}
+
+	for (prevp = ps_head, curp = ps_head->pe_next; curp != NULL;
+	    prevp = curp, curp = curp->pe_next) {
+		if (curp == delp)
+			break;
+	}
+	if (curp == NULL) {
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		return (0);
+	}
+
+	prevp->pe_next = curp->pe_next;
+	if (curp == ps_tail)
+		ps_tail = prevp;
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	PE_REFRELE(curp);
+	return (1);
+}
+
+/*
+ * return -1 on error, number of preshared entries written on success
+ * Holds ps_rwlock as reader
+ */
+int
+write_preshared(int fd, char **errmp)
+{
+	preshared_entry_t	*pep;
+	FILE	*ofile;
+	char	*keyp;
+	int	written = 0;
+
+	(void) pthread_rwlock_rdlock(&ps_rwlock);
+	if ((ofile = fdopen(fd, "w+")) == NULL) {
+		*errmp = strerror(errno);
+		(void) pthread_rwlock_unlock(&ps_rwlock);
+		return (-1);
+	}
+
+	for (pep = ps_head; pep != NULL; pep = pep->pe_next) {
+		const char *mdstr;
+
+		if ((keyp = getkeystr(pep->pe_keybuf, pep->pe_keybuf_bytes,
+		    pep->pe_keybuf_lbits)) == NULL) {
+			*errmp = strerror(errno);
+			(void) pthread_rwlock_unlock(&ps_rwlock);
+			return (-1);
+		}
+
+		if (pep->pe_locid != NULL) {
+			(void) fprintf(ofile, "{\n"
+			    "\tlocalidtype    %s\n\tlocalid        %s\n",
+			    getidstr(pep->pe_locidtype), pep->pe_locid);
+		} else {
+			(void) fprintf(ofile,
+			    "{\n\tlocalidtype    %s\n\tlocalid        ",
+			    getidstr(pep->pe_locidtype));
+			(void) dump_sockaddr(
+			    (struct sockaddr *)&pep->pe_locid_sa,
+			    (pep->pe_locid_plen > 0) ? pep->pe_locid_plen : 0,
+			    B_TRUE, ofile, B_FALSE);
+			(void) fprintf(ofile, "\n");
+		}
+
+		if (pep->pe_remid != NULL) {
+			(void) fprintf(ofile,
+			    "\tremoteidtype   %s\n\tremoteid       %s\n",
+			    getidstr(pep->pe_remidtype), pep->pe_remid);
+		} else {
+			(void) fprintf(ofile,
+			    "\tremoteidtype   %s\n\tremoteid       ",
+			    getidstr(pep->pe_remidtype));
+			(void) dump_sockaddr(
+			    (struct sockaddr *)&pep->pe_remid_sa,
+			    (pep->pe_remid_plen > 0) ? pep->pe_remid_plen : 0,
+			    B_TRUE, ofile, B_FALSE);
+			(void) fprintf(ofile, "\n");
+		}
+
+		mdstr = getikmstr(pep->pe_ike_mode);
+		if (mdstr != NULL)
+			(void) fprintf(ofile, "\tike_mode       %s\n", mdstr);
+
+		(void) fprintf(ofile, "\tkey            %s\n}\n", keyp);
+		free(memset(keyp, 0, strlen(keyp)));
+		written++;
+	}
+	(void) fclose(ofile);
+
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (written);
+}
+
+/*
+ * Convert a PS_ID_* to an SADB_IDENTTYPE_* constant.
+ * Returns SADB_IDENTTYPE_RESERVED on failure.
+ */
+int
+psid2sadb(int psid)
+{
+	switch (psid) {
+	case PS_ID_IP:
+	case PS_ID_IP4:
+	case PS_ID_IP6:
+		return (SADB_IDENTTYPE_RESERVED);
+	case PS_ID_SUBNET:
+	case PS_ID_SUBNET4:
+	case PS_ID_SUBNET6:
+		return (SADB_IDENTTYPE_PREFIX);
+	case PS_ID_RANGE:
+	case PS_ID_RANGE4:
+	case PS_ID_RANGE6:
+		return (SADB_X_IDENTTYPE_ADDR_RANGE);
+	case PS_ID_ASN1DN:
+		return (SADB_X_IDENTTYPE_DN);
+	case PS_ID_ASN1GN:
+		return (SADB_X_IDENTTYPE_GN);
+	case PS_ID_KEYID:
+		return (SADB_X_IDENTTYPE_KEY_ID);
+	case PS_ID_FQDN:
+		return (SADB_IDENTTYPE_FQDN);
+	case PS_ID_USER_FQDN:
+		return (SADB_IDENTTYPE_USER_FQDN);
+	}
+	return (SADB_IDENTTYPE_RESERVED);
+}
+
+int
+sadb2psid(int sadb)
+{
+	switch (sadb) {
+	case SADB_IDENTTYPE_PREFIX:
+		return (PS_ID_SUBNET);
+	case SADB_X_IDENTTYPE_ADDR_RANGE:
+		return (PS_ID_RANGE);
+	case SADB_X_IDENTTYPE_DN:
+		return (PS_ID_ASN1DN);
+	case SADB_X_IDENTTYPE_GN:
+		return (PS_ID_ASN1GN);
+	case SADB_X_IDENTTYPE_KEY_ID:
+		return (PS_ID_KEYID);
+	case SADB_IDENTTYPE_FQDN:
+		return (PS_ID_FQDN);
+	case SADB_IDENTTYPE_USER_FQDN:
+		return (PS_ID_USER_FQDN);
+	}
+	return (0);
+}
+
+/*
+ * Lookup preshared entry by ident
+ * Holds ps_rwlock as reader
+ */
+preshared_entry_t *
+lookup_ps_by_ident(sadb_ident_t *local, sadb_ident_t *remote)
+{
+	int	ltype, rtype;
+	char	*lid, *rid;
+	preshared_entry_t	*ps;
+
+	if (local == NULL || remote == NULL)
+		return (NULL);
+
+	lid = (char *)(local + 1);
+	rid = (char *)(remote + 1);
+
+	(void) pthread_rwlock_rdlock(&ps_rwlock);
+	for (ps = ps_head; ps != NULL; ps = ps->pe_next) {
+
+		/*
+		 * Have to convert for each preshared (rather than converting
+		 * the passed-in sadb ids just once to preshared-style)
+		 * because there are multiple PS_ID_* for each SADB_IDENT_*.
+		 */
+		ltype = psid2sadb(ps->pe_locidtype);
+		rtype = psid2sadb(ps->pe_remidtype);
+
+		if ((local->sadb_ident_type != ltype) ||
+		    (remote->sadb_ident_type != rtype))
+			continue;
+
+		if (ps->pe_locidtype == PS_ID_FQDN ||
+		    ps->pe_locidtype == PS_ID_USER_FQDN) {
+			/*
+			 * Check for case insensitive match.
+			 */
+			if ((strcasecmp(ps->pe_locid, lid) == 0) &&
+			    (strcasecmp(ps->pe_remid, rid) == 0))
+				break;
+		} else {
+			/*
+			 * Otherwise just go for an exact match.
+			 */
+			if ((strcmp(ps->pe_locid, lid) == 0) &&
+			    (strcmp(ps->pe_remid, rid) == 0))
+				break;
+		}
+	}
+	if (ps != NULL)
+		PE_REFHOLD(ps);
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (ps);
+}
+
+/*
+ * Stolen from kernel spd.c - maybe this should go in libipsecutil
+ *
+ * sleazy prefix-length-based compare.
+ */
+boolean_t
+ip_addr_match(uint8_t *addr1, int pfxlen, in6_addr_t *addr2p)
+{
+	int offset = pfxlen>>3;
+	int bitsleft = pfxlen & 7;
+	uint8_t *addr2 = (uint8_t *)addr2p;
+
+	/*
+	 * and there was much evil..
+	 * XXX should inline-expand the bcmp here and do this 32 bits
+	 * or 64 bits at a time..
+	 */
+	return ((bcmp(addr1, addr2, offset) == 0) &&
+	    ((bitsleft == 0) ||
+	    (((addr1[offset] ^ addr2[offset]) & (0xff<<(8-bitsleft))) == 0)));
+}
+
+/*
+ * Lookup preshared entry by v4 or v4 mapped v6 address
+ * Holds ps_rwlock as reader
+ */
+preshared_entry_t *
+lookup_ps_by_in_addr(struct in_addr *local, struct in_addr *remote)
+{
+	struct in_addr *inp;
+	struct in_addr in;
+	struct sockaddr_in	*loc_sin, *rem_sin;
+	struct sockaddr_in6	*loc_sin6, *rem_sin6;
+	preshared_entry_t *ps;
+	boolean_t loc_is_v6, rem_is_v6;
+	int pfxlen;
+
+	if (local == NULL || remote == NULL)
+		return (NULL);
+
+	bzero(&in, sizeof (struct in_addr));
+
+	(void) pthread_rwlock_rdlock(&ps_rwlock);
+	/*
+	 * Also looks up IPv6 id type entries for mapped addresses
+	 */
+
+	for (ps = ps_head; ps != NULL; ps = ps->pe_next) {
+
+		/* filter on relevant id type first */
+		if ((ps->pe_locidtype != PS_ID_IP4 &&
+		    ps->pe_locidtype != PS_ID_IP6 &&
+		    ps->pe_locidtype != PS_ID_IP &&
+		    ps->pe_locidtype != PS_ID_SUBNET4 &&
+		    ps->pe_locidtype != PS_ID_SUBNET6 &&
+		    ps->pe_locidtype != PS_ID_SUBNET) ||
+		    (ps->pe_remidtype != PS_ID_IP4 &&
+		    ps->pe_remidtype != PS_ID_IP6 &&
+		    ps->pe_remidtype != PS_ID_IP &&
+		    ps->pe_remidtype != PS_ID_SUBNET4 &&
+		    ps->pe_remidtype != PS_ID_SUBNET6 &&
+		    ps->pe_remidtype != PS_ID_SUBNET))
+			continue; /* local and/or remote id types invalid */
+
+		/* establish format of address */
+		if ((ps->pe_locidtype == PS_ID_IP6 ||
+		    ps->pe_locidtype == PS_ID_SUBNET6) ||
+		    ((ps->pe_locidtype == PS_ID_IP ||
+		    ps->pe_locidtype == PS_ID_SUBNET) &&
+		    check_if_v6(ps->pe_locid)))
+			loc_is_v6 = B_TRUE;
+		else
+			loc_is_v6 = B_FALSE;
+		if ((ps->pe_remidtype == PS_ID_IP6 ||
+		    ps->pe_remidtype == PS_ID_SUBNET6) ||
+		    ((ps->pe_locidtype == PS_ID_IP ||
+		    ps->pe_locidtype == PS_ID_SUBNET) &&
+		    check_if_v6(ps->pe_remid)))
+			rem_is_v6 = B_TRUE;
+		else
+			rem_is_v6 = B_FALSE;
+
+		loc_sin = (struct sockaddr_in *)&ps->pe_locid_sa;
+		rem_sin = (struct sockaddr_in *)&ps->pe_remid_sa;
+		loc_sin6 = (struct sockaddr_in6 *)&ps->pe_locid_sa;
+		rem_sin6 = (struct sockaddr_in6 *)&ps->pe_remid_sa;
+
+		/* if v6 format, it is only interesting if v4mapped */
+		if (loc_is_v6 && !IN6_IS_ADDR_V4MAPPED(&loc_sin6->sin6_addr))
+			continue;	/* not v4 mapped v6 address */
+		if (rem_is_v6 && !IN6_IS_ADDR_V4MAPPED(&rem_sin6->sin6_addr))
+			continue;	/* not v4 mapped v6 address */
+
+
+		/* match localid */
+		if (loc_is_v6) {
+			IN6_V4MAPPED_TO_INADDR(&loc_sin6->sin6_addr, &in);
+			inp = &in;
+		} else {
+			inp = &loc_sin->sin_addr;
+		}
+
+		if (ps->pe_locid_plen < 0)
+			pfxlen = ADDRBITS_V4;
+		else
+			pfxlen = ps->pe_locid_plen;
+		if (!ip_addr_match((uint8_t *)&inp->s_addr, pfxlen,
+		    (in6_addr_t *)&local->s_addr))
+			continue; /* local id mismatched */
+
+		/* match remote id */
+		if (rem_is_v6) {
+			IN6_V4MAPPED_TO_INADDR(&rem_sin6->sin6_addr, &in);
+			inp = &in;
+		} else {
+			inp = &rem_sin->sin_addr;
+		}
+
+		if (ps->pe_remid_plen < 0)
+			pfxlen = ADDRBITS_V4;
+		else
+			pfxlen = ps->pe_remid_plen;
+		if (!ip_addr_match((uint8_t *)&inp->s_addr, pfxlen,
+		    (in6_addr_t *)&remote->s_addr))
+			continue; /* remote id mismatched */
+
+		/* match found for both local and remote id */
+		break; /* out of loop */
+	}
+	if (ps != NULL)
+		PE_REFHOLD(ps);
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (ps); /* NULL if loop expires on its own */
+}
+
+/*
+ * Lookup preshared entry by v6 address
+ * Holds ps_rwlock as reader
+ */
+preshared_entry_t *
+lookup_ps_by_in6_addr(struct in6_addr *local, struct in6_addr *remote)
+{
+	preshared_entry_t *ps;
+	struct sockaddr_in6	*loc_sin6, *rem_sin6;
+	int pfxlen;
+
+	if (local == NULL || remote == NULL)
+		return (NULL);
+
+	(void) pthread_rwlock_rdlock(&ps_rwlock);
+	for (ps = ps_head; ps != NULL; ps = ps->pe_next) {
+
+		/* filter on relevant id type first */
+		if ((ps->pe_locidtype != PS_ID_IP6 &&
+		    ps->pe_locidtype != PS_ID_IP &&
+		    ps->pe_locidtype != PS_ID_SUBNET &&
+		    ps->pe_locidtype != PS_ID_SUBNET6) ||
+		    (ps->pe_remidtype != PS_ID_IP6 &&
+		    ps->pe_remidtype != PS_ID_IP &&
+		    ps->pe_remidtype != PS_ID_SUBNET &&
+		    ps->pe_remidtype != PS_ID_SUBNET6))
+			continue; /* local and/or remote id types invalid */
+
+		/*
+		 * if PS_ID_IP or PS_ID_SUBNET, it is
+		 * only interesting if v6 format
+		 */
+		if ((ps->pe_locidtype == PS_ID_IP ||
+		    ps->pe_locidtype == PS_ID_SUBNET) &&
+		    !check_if_v6(ps->pe_locid))
+			continue; /* not v6 address */
+		if ((ps->pe_remidtype == PS_ID_IP ||
+		    ps->pe_remidtype == PS_ID_SUBNET) &&
+		    !check_if_v6(ps->pe_remid))
+			continue; /* not v6 address */
+
+		loc_sin6 = (struct sockaddr_in6 *)&ps->pe_locid_sa;
+		rem_sin6 = (struct sockaddr_in6 *)&ps->pe_remid_sa;
+
+		if (ps->pe_locid_plen < 0)
+			pfxlen = ADDRBITS_V6;
+		else
+			pfxlen = ps->pe_locid_plen;
+		if (!ip_addr_match((uint8_t *)&loc_sin6->sin6_addr, pfxlen,
+		    (in6_addr_t *)local))
+			continue; /* local id mismatched */
+
+		if (ps->pe_remid_plen < 0)
+			pfxlen = ADDRBITS_V6;
+		else
+			pfxlen = ps->pe_remid_plen;
+		if (!ip_addr_match((uint8_t *)&rem_sin6->sin6_addr, pfxlen,
+		    (in6_addr_t *)remote))
+			continue; /* remote id mismatched */
+		/* match found for both local and remote id */
+		break; /* out of loop */
+	}
+	if (ps != NULL)
+		PE_REFHOLD(ps);
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (ps); /* NULL if loop expires on its own */
+}
+
+/*
+ * Lookup nth preshared key entry
+ * Holds ps_rwlock as reader
+ */
+preshared_entry_t *
+lookup_nth_ps(int n)
+{
+	preshared_entry_t	*ps;
+	int			cnt;
+
+	(void) pthread_rwlock_rdlock(&ps_rwlock);
+	for (cnt = 0, ps = ps_head; (cnt < n) && (ps != NULL);
+	    cnt++, ps = ps->pe_next)
+		;
+	if (ps != NULL)
+		PE_REFHOLD(ps);
+	(void) pthread_rwlock_unlock(&ps_rwlock);
+	return (ps);
+}
+
+/*
+ * Actual freeing function.
+ */
+void
+free_preshared(preshared_entry_t *pe)
+{
+	assert(pe->pe_refcnt == 0);
+	free(pe->pe_locid);
+	free(pe->pe_remid);
+	/* Use memset()'s return value to our advantage. */
+	free(memset(pe->pe_keybuf, 0, pe->pe_keybuf_bytes));
+	free(pe);
+}
+
+/*
+ * Read in a list of pre-shared keys.  Called from main() when the daemon first
+ * starts.
+ */
+void
+preshared_init(boolean_t ignore_errors)
+{
+	const char *filename = PRESHARED_KEY_FILE;
+	char *errorstr;
+
+	bunyan_info(log, "Loading preshared keys", BUNYAN_T_END);
+	errorstr = preshared_load(filename, -1, B_TRUE);
+
+	if (errorstr != NULL) {
+		if (strncmp(errorstr, "DUP", 3) == 0)
+			errorstr = gettext("Duplicate entries ignored\n");
+		/*
+		 * Error handling... Print to terminal, if exists.
+		 * Debug logging already taken care of by other functions.
+		 * Don't exit because we want to load other policy.
+		 */
+		bunyan_error(log, "Error reading preshared file",
+		    BUNYAN_T_STRING, "filename", filename,
+		    BUNYAN_T_STRING, "errmsg", errorstr);
+		if (!ignore_errors)
+			err(EXIT_FAILURE, "Fatal errors in %s", filename);
+	}
+}
+
+/*
+ * Called when the daemon catches SIGHUP.
+ * Currently equivalent to preshared_init().
+ */
+void
+preshared_reload(void)
+{
+	preshared_init(B_FALSE);
+}
+
+/*
+ * Random Notes:
+ * (a)
+ * Maybe a better syntax for fields is following which eliminates
+ * semantic relationships between two different fields (id and idtype) ???
+ * E.g.
+ * {
+ *	...
+ *	localid	ipv4		1.2.3.4
+ *	remoteid rangev4	1.2.3.4-5.6.7.8
+ *	...
+ *  }
+ *  Current design causes us to delay certain verifications into a
+ *  postprocess_XXX() routine which otherwise could be done while
+ *  parsing if syntax was as above.
+ *
+ * (b)
+ *	Current error handling has a generic design that prints only
+ *	error message, entry number and line number. Other options are
+ *	possible (e.g. parsekey() "original" code from danmcd prints
+ *	error messages specific to the error context which have more
+ *	detailed context). Perhaps we should change to direct printing
+ *	of specific error messages globally later, but for now stick
+ *	to the simple model coded above.
+ * (c) The getnexttoken() part can be improved by passing it some context
+ *	such as "expected token" and maybe the fieldname. This *may* help
+ *	allow a more flexible syntax for complex tokens such as ranges.
+ *	which cannot currently embed spaces but it would be nice to allow
+ *	them to do that and be delimited with a context sensitive delimiter.
+ * (d) Needs detection of duplicates in the database before inserting new
+ *	entry.
+ * (e) Can be enhanced to do hashes and better structures than a linked list
+ *	of entries.
+ */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/preshared.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,212 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#ifndef	_PRESHARED_H
+#define	_PRESHARED_H
+
+#ifdef	__cplusplus
+extern "C" {
+#endif
+
+#include <sys/types.h>
+#include <sys/atomic.h>
+#include <netinet/in.h>
+#include <net/pfkeyv2.h>
+
+#define	PRESHARED_KEY_FILE	"/etc/inet/secret/ike.preshared"
+
+typedef struct preshared_entry_s {
+	struct preshared_entry_s	*pe_next;
+	int				pe_flds_mask;
+	int				pe_locidtype;
+	char				*pe_locid;
+	int				pe_remidtype;
+	char				*pe_remid;
+	int				pe_ike_mode;
+	uint8_t				*pe_keybuf;
+	uint_t				pe_keybuf_bytes;
+	uint_t				pe_keybuf_lbits;
+	struct sockaddr_storage		pe_locid_sa;
+	struct sockaddr_storage		pe_remid_sa;
+	int				pe_locid_plen;
+	int				pe_remid_plen;
+	volatile uint32_t		pe_refcnt;
+} preshared_entry_t;
+
+#define	PE_REFHOLD(pe)	atomic_inc_32(&(pe)->pe_refcnt)
+
+/* Stupid C tricks stolen from <assert.h>. */
+#define	PE_REFRELE(pe)	\
+	(void) ((atomic_dec_32_nv(&(pe)->pe_refcnt) != 0) ||	\
+	    (free_preshared(pe), 0))
+
+/*
+ * Types of Fields
+ * Note: used in pe_flds_mask; values bitwise distinct, not just unique
+ */
+#define	PS_FLD_COMMENT			0x01
+#define	PS_FLD_LOCID			0x02
+#define	PS_FLD_LOCID_TYPE		0x04
+#define	PS_FLD_REMID			0x08
+#define	PS_FLD_REMID_TYPE		0x10
+#define	PS_FLD_IKE_MODE			0x20
+#define	PS_FLD_KEY			0x40
+
+/*
+ * Type of Remote/Local Ids
+ * Note: used in pe_locidtype and pe_remidtype fields.
+ */
+#define	PS_ID_IP			1
+#define	PS_ID_IP4			2
+#define	PS_ID_IP6			3
+#define	PS_ID_SUBNET			4
+#define	PS_ID_SUBNET4			5
+#define	PS_ID_SUBNET6			6
+#define	PS_ID_RANGE4			7
+#define	PS_ID_RANGE6			8
+#define	PS_ID_ASN1DN			9
+#define	PS_ID_ASN1GN			10
+#define	PS_ID_KEYID			11
+#define	PS_ID_FQDN			12
+#define	PS_ID_USER_FQDN			13
+#define	PS_ID_RANGE			14
+
+
+/*
+ * Types of IKE Modes
+ * Note: used in pe_ike_mode field.
+ */
+#define	PS_IKM_MAIN			1
+#define	PS_IKM_AGGRESSIVE		2
+#define	PS_IKM_BOTH			3
+
+/*
+ * Prefix length "special values"
+ * Note: used in pe_locid_plen and pe_remid_plen fields
+ */
+#define	PS_PLEN_BAD_ADDR	-1	/* prefix is invalid */
+#define	PS_PLEN_NO_PREFIX	-2	/* no prefix was found */
+
+/*
+ * Interface function prototypes
+ *
+ */
+
+/*
+ * char *preshared_load()
+ *	args : char *ps_filename:	config file name
+ *	args : int  fd:			config file descriptor; will
+ *					be used if ps_filename is NULL.
+ *	args : boolean_t replace:	true => replace existing list;
+ *					false => append to existing list
+ * Return value
+ *	- NULL on success; pointer to error string on error.
+ *
+ *	Also on error, globals err_line_number/err_entry_number point
+ *	to approximate location of error.
+ */
+extern char *preshared_load(const char *, int, boolean_t);
+
+/*
+ * Append the given preshared_entry_t to the global list.
+ * Will NOT increase refcnt - the caller must refhold and let the list
+ * take over the reference.
+ */
+extern boolean_t append_preshared_entry(preshared_entry_t *);
+
+/*
+ * Actual freeing function.  Almost always called by the REFRELE macro.
+ */
+extern void free_preshared(preshared_entry_t *);
+
+/*
+ * psid2sadb(): convert PS_ID_* types to SADB_[X_]IDENTTYPE_* types
+ */
+extern int psid2sadb(int);
+
+/*
+ * Look up preshared entries by in_addr (IPv4)
+ *	- first arg localid
+ *	- second arg remoteid
+ *
+ * Returns a reference-held entry.
+ */
+extern preshared_entry_t *lookup_ps_by_in_addr(struct in_addr *,
+    struct in_addr *);
+
+/*
+ * Look up preshared entries by in_addr (IPv6)
+ *	- first arg localid
+ *	- second arg remoteid
+ *
+ * Returns a reference-held entry.
+ */
+extern preshared_entry_t *lookup_ps_by_in6_addr(struct in6_addr *,
+    struct in6_addr *);
+
+/*
+ * Look up preshared entries by identity
+ *	- first arg localid
+ *	- second arg remoteid
+ *
+ * Returns a reference-held entry.
+ */
+extern preshared_entry_t *lookup_ps_by_ident(sadb_ident_t *, sadb_ident_t *);
+
+/*
+ * Look up the nth preshared entry in our list
+ *	- first arg n
+ *
+ * Returns a reference-held entry.
+ */
+extern preshared_entry_t *lookup_nth_ps(int);
+
+/*
+ * Delete an entry from the list of preshareds.
+ *	- first arg points to the entry to be deleted
+ *	- returns 1 if entry successfully deleted; 0 if entry not found
+ */
+extern int delete_ps(preshared_entry_t *);
+
+/*
+ * Write the preshared entries out to a specified file
+ *	args : int  fd:			config file descriptor
+ *	args : char **errmp:		errpr message string
+ * Return value
+ *	- if the list was written successfully, returns the number
+ *	  of entries that were written; returns -1 on error.
+ *	  Also on error, errmp contains pointer to error message
+ *	  string and globals err_line_number/err_entry_number point
+ *	  to approximate location of error.
+ */
+extern int write_preshared(int, char **);
+
+#ifdef	__cplusplus
+}
+#endif
+
+#endif	/* _PRESHARED_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/prf.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,361 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.
+ */
+#include <sys/debug.h>
+#include <security/cryptoki.h>
+#include <umem.h>
+#include <limits.h>
+#include <string.h>
+#include "defs.h"
+#include "ikev2.h"
+#include "prf.h"
+#include "pkcs11.h"
+
+typedef struct prf_alg_s {
+	int			i2alg;
+	CK_MECHANISM_TYPE	hash;
+	CK_MECHANISM_TYPE	hmac;
+	size_t			inlen;	/* in bytes */
+	size_t			outlen; /* in bytes */
+	boolean_t		pad;
+} prf_alg_t;
+
+#define	MECHPAD(_i2, _p11, _in, _out) { 	\
+	.i2alg = IKEV2_PRF_HMAC_ ## _i2,	\
+	.hash = CKM_ ## _p11,			\
+	.hmac = CKM_ ## _p11 ## _HMAC,		\
+	.inlen = _in,				\
+	.outlen = _out,				\
+	.pad = B_TRUE				\
+}
+
+static const prf_alg_t prf_tbl[] = {
+	MECHPAD(MD5, MD5, 64, 16),
+	MECHPAD(SHA1, SHA_1, 64, 16),
+	MECHPAD(SHA2_256, SHA256, 64, 32),
+	MECHPAD(SHA2_384, SHA384, 128, 48),
+	MECHPAD(SHA2_512, SHA512, 128, 64)
+};
+#define	N_PRF (sizeof (prf_tbl) / sizeof (prf_alg_t))
+
+static const prf_alg_t	*get_alg(int);
+static CK_RV		prfplus_update(prfp_t *);
+
+/*
+ * Create a PKCS11 key object that can be used in C_Sign* operations with
+ * a scatter/gather-like listing of source input.
+ *
+ * Args:
+ * 	alg	The PRF algorithm this will be used with
+ * 	src	An array of buf_t's pointing to the source key
+ * 	n	The number of buf_t's we have
+ *	kp	Pointer to CK_OBJECT_HANDLE to write the resulting key
+ *		object.
+ * Returns:
+ * 	CKR_OK	Success
+ */
+CK_RV
+prf_genkey(int alg, buf_t *restrict src, size_t n,
+    CK_OBJECT_HANDLE_PTR restrict kp)
+{
+	const prf_alg_t	*algp;
+
+	CK_RV			rc;
+	CK_OBJECT_CLASS		cls = CKO_SECRET_KEY;
+	CK_KEY_TYPE		kt = CKK_GENERIC_SECRET;
+	CK_BBOOL		false_v = CK_FALSE;
+	CK_MECHANISM_TYPE	hmac;
+	CK_ATTRIBUTE		template[] = {
+		{ CKA_VALUE, NULL_PTR, 0 },	/* filled in later */
+		{ CKA_CLASS, &cls, sizeof (cls) },
+		{ CKA_KEY_TYPE, &kt, sizeof (kt) },
+		/* XXX: is this actually needed? */
+		{ CKA_ALLOWED_MECHANISMS, &hmac, sizeof (hmac) },
+		{ CKA_MODIFIABLE, &false_v, sizeof (false_v) }
+	};
+	buf_t			key = { 0 };
+	size_t			srclen;
+	ulong_t			keylen = 0; /* actual length */
+
+	rc = CKR_OK;
+
+	VERIFY((algp = get_alg(alg)) != NULL);
+	hmac = algp->hmac;
+
+	srclen = 0;
+	for (size_t i = 0; i < n; i++)
+		srclen += buf_left(&src[i]);
+
+	if (srclen < algp->inlen && algp->pad)
+		keylen = algp->inlen;
+	else
+		keylen = srclen;
+
+	if (!buf_alloc(&key, keylen)) {
+		rc = CKR_HOST_MEMORY;
+		goto done;
+	}
+	buf_set_write(&key);
+
+	/*
+	 * The HMAC standards specify what an implementation should do when
+	 * the given key length doesn't match the preferred key length (either
+	 * pad or run the digest alg on the key to yield a value of the desired
+	 * length), so we should not need to worry about this.
+	 */
+	VERIFY3U(buf_copy(&key, src, n), <=, keylen);
+
+	template[0].pValue = key.b_buf;
+	template[0].ulValueLen = keylen;
+
+	rc = C_CreateObject(p11h, template,
+	    sizeof (template) / sizeof (CK_ATTRIBUTE), kp);
+
+done:
+	buf_free(&key);
+	return (rc);
+}
+
+/*
+ * Run the given PRF algorithm for the given key and seed and place
+ * result into out.
+ */
+CK_RV
+prf(int alg, CK_OBJECT_HANDLE key, buf_t *restrict seed, size_t nseed,
+    buf_t *restrict out)
+{
+	const prf_alg_t		*algp;
+	CK_MECHANISM		mech;
+	CK_RV			rc = CKR_OK;
+	CK_ULONG		len;
+
+	VERIFY3P((algp = get_alg(alg)), !=, NULL);
+	VERIFY3U(out->b_len, >=, algp->outlen);
+
+	mech.mechanism = algp->hmac;
+	mech.pParameter = NULL;
+	mech.ulParameterLen = 0;
+
+	if ((rc = C_SignInit(p11h, &mech, key)) != CKR_OK)
+		return (rc);
+
+	for (size_t i = 0; i < nseed; i++, seed) {
+		BUF_IS_READ(seed);
+		rc = C_SignUpdate(p11h, seed->b_ptr, buf_left(seed));
+		/* XXX: should we still call C_SignFinal? */
+		if (rc != CKR_OK)
+			return (rc);
+	}
+
+	BUF_IS_WRITE(out);
+
+	len = buf_left(out);
+	rc = C_SignFinal(p11h, out->b_ptr, &len);
+	if (rc == CKR_OK)
+		VERIFY3U(len, ==, buf_left(out));
+
+	buf_skip(out, len);
+	return (rc);
+}
+
+/*
+ * Inititalize a prf+ instance for the given algorithm, key, and seed.
+ */
+CK_RV
+prfplus_init(prfp_t *restrict prfp, int alg, CK_OBJECT_HANDLE key,
+    const buf_t *restrict seed)
+{
+	const prf_alg_t	*algp;
+	CK_RV		rc = CKR_OK;
+
+	(void) memset(prfp, 0, sizeof (*prfp));
+
+	VERIFY((algp = get_alg(alg)) != NULL);
+
+	if (!buf_alloc(&prfp->tbuf[0], algp->outlen) ||
+	    !buf_alloc(&prfp->tbuf[1], algp->outlen) ||
+	    !buf_alloc(&prfp->seed, buf_left(seed))) {
+		rc = CKR_HOST_MEMORY;
+		goto error;
+	}
+
+	/* stash our own copy of the seed */
+	(void) buf_copy(&prfp->seed, seed, 1);
+	VERIFY(!buf_eof(&prfp->seed));
+
+	/*
+	 * Per RFC5996 2.13, prf+(K, S) = T1 | T2 | T3 | T4 | ...
+	 *
+	 * where:
+	 * 	T1 = prf (K, S | 0x01)
+	 * 	T2 = prf (K, T1 | S | 0x02)
+	 * 	T3 = prf (K, T2 | S | 0x03)
+	 * 	T4 = prf (K, T3 | S | 0x04)
+	 *
+	 * As such, we keep a list of buf_t's for each of the three components
+	 * Since the last two never change location, they are set now, while
+	 * the first points to either prfp->tbuf[0] or prfp->tbuf[1], based
+	 * on the value of prfp->n
+	 */
+	prfp->prf_arg[1] = prfp->seed;
+	prfp->prf_arg[2].b_ptr = prfp->prf_arg[2].b_buf = &prfp->n;
+	prfp->prf_arg[2].b_len = sizeof (prfp->n);
+	prfp->n = 1;
+
+	buf_set_read(&prfp->prf_arg[1]);
+	buf_set_read(&prfp->prf_arg[2]);
+
+	/*
+	 * Fill prfp->tbuf[1] with T1. T1 is defined as:
+	 * 	T1 = prf (K, S | 0x01)
+	 * Note that this is different from subsequent iterations, hence
+	 * starting at prfp->prf_arg[1], not prfp->arg[0]
+	 */
+	rc = prf(alg, prfp->key, &prfp->prf_arg[1], 2, &prfp->tbuf[1]);
+	return (rc);
+
+error:
+	prfplus_fini(prfp);
+	return (rc);
+}
+
+/*
+ * Fill out with the result of the prf+ function.
+ */
+CK_RV
+prfplus(prfp_t *restrict prfp, buf_t *restrict out)
+{
+	const prf_alg_t	*algp;
+	buf_t		t;
+	buf_t		outcopy;
+	CK_RV		rc = CKR_OK;
+
+	algp = get_alg(prfp->i2alg);
+
+	/* generate a local cache of out so we can manipulate the ptr and len */
+	outcopy = *out;
+	buf_set_write(&outcopy);
+
+	while (buf_left(&outcopy) > 0) {
+		size_t chunk;
+
+		chunk = buf_left(&outcopy);
+
+		if (prfp->n & 0x01)
+			t = prfp->tbuf[1];
+		else
+			t = prfp->tbuf[0];
+
+		t.b_ptr += prfp->pos;
+		t.b_len -= prfp->pos;
+
+		if (t.b_len == 0) {
+			if ((rc = prfplus_update(prfp)) != CKR_OK)
+				goto done;
+			continue;
+		}
+
+		if (chunk > t.b_len)
+			chunk = t.b_len;
+
+		VERIFY(buf_copy(&outcopy, &t, chunk) == chunk);
+		buf_skip(&outcopy, chunk);
+		prfp->pos += chunk;
+	}
+
+done:
+	return (rc);
+}
+
+/*
+ * Perform a prf+ iteration
+ */
+static CK_RV
+prfplus_update(prfp_t *prfp)
+{
+	buf_t	*dest;
+	CK_RV	rc = CKR_OK;
+
+	ASSERT(prfp->n >= 1);
+
+	if (prfp->n == 0xff) {
+		/* XXX: log error */
+		return (CKR_GENERAL_ERROR);
+	}
+
+	if (++prfp->n & 0x01) {
+		prfp->prf_arg[1] = prfp->tbuf[1];
+		dest = &prfp->tbuf[0];
+	} else {
+		prfp->prf_arg[0] = prfp->tbuf[0];
+		dest = &prfp->tbuf[1];
+	}
+
+	rc = prf(prfp->i2alg, prfp->key, prfp->prf_arg, 3, dest);
+	prfp->pos = 0;
+	return (rc);
+}
+
+void
+prfplus_fini(prfp_t *prfp)
+{
+	if (prfp == NULL)
+		return;
+
+	buf_free(&prfp->tbuf[0]);
+	buf_free(&prfp->tbuf[1]);
+	buf_free(&prfp->seed);
+	(void) memset(prfp, 0, sizeof (*prfp));
+}
+
+CK_MECHANISM_TYPE
+ikev2_prf_to_p11(int prf)
+{
+	switch (prf) {
+	case IKEV2_PRF_HMAC_MD5:
+		return (CKM_MD5_HMAC);
+	case IKEV2_PRF_HMAC_SHA1:
+		return (CKM_SHA_1_HMAC);
+	case IKEV2_PRF_HMAC_SHA2_256:
+		return (CKM_SHA256_HMAC);
+	case IKEV2_PRF_HMAC_SHA2_384:
+		return (CKM_SHA384_HMAC);
+	case IKEV2_PRF_HMAC_SHA2_512:
+		return (CKM_SHA512_HMAC);
+	}
+
+	INVALID("invalid hmac value");
+
+	/*NOTREACHED*/
+	return (0);
+}
+
+size_t
+ikev2_prf_keylen(int prf)
+{
+	return (get_alg(prf)->inlen);
+}
+
+/*
+ * Get the information for a given algorithm, or NULL of not found
+ */
+static const prf_alg_t *
+get_alg(int i2alg)
+{
+	for (int i = 0; i < N_PRF; i++) {
+		if (prf_tbl[i].i2alg == i2alg)
+			return (&prf_tbl[i]);
+	}
+	return (NULL);
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/prf.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,59 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Jason King
+ * Copyright (c) 2017, Joyent, Inc.
+ */
+
+#ifndef _PRF_H
+#define	_PRF_H
+
+#include <sys/types.h>
+#include <security/cryptoki.h>
+#include "buf.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#ifndef _PRFP_T
+#define	_PRFP_T
+struct prfp_s {
+	CK_OBJECT_HANDLE	key;
+	int			i2alg;
+	buf_t			tbuf[2];
+	buf_t			seed;
+	buf_t			prf_arg[3];
+	size_t			pos;
+	uint8_t			n;
+};
+typedef struct prfp_s prfp_t;
+#endif /* _PRFP_T */
+
+/* These are internal to in.ikev2d, so don't bother with ugly !C99 compat */
+CK_RV	prf_key(CK_MECHANISM_TYPE, buf_t *restrict, size_t,
+	    CK_OBJECT_HANDLE_PTR restrict);
+
+CK_RV	prf(int, CK_OBJECT_HANDLE, buf_t *restrict, size_t, buf_t *restrict);
+
+CK_RV	prfplus_init(prfp_t *restrict, int, CK_OBJECT_HANDLE,
+	    const buf_t *restrict);
+void	prfplus_fini(prfp_t *);
+CK_RV	prfplus(prfp_t *restrict, buf_t *restrict);
+
+size_t	ikev2_prf_keylen(int);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _PRF_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/random.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,105 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright (c) 2017 Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <locale.h>
+#include <ipsec_util.h>
+#include "random.h"
+#include "defs.h"
+
+/* Start off with invalid values until init */
+static int low_random = -1;
+static int high_random = -1;
+
+void
+random_init(void)
+{
+	if ((low_random = open("/dev/urandom", 0)) == -1)
+		err(EXIT_FAILURE, "/dev/urandom open failed");
+
+	if ((high_random = open("/dev/random", 0)) == -1)
+		err(EXIT_FAILURE, "/dev/random open failed");
+}
+
+uint64_t
+random_high_64(void)
+{
+	uint64_t rc;
+
+	random_high(&rc, sizeof (rc));
+	return (rc);
+}
+
+uint64_t
+random_low_64(void)
+{
+	uint64_t rc;
+
+	random_low(&rc, sizeof (rc));
+	return (rc);
+}
+
+void
+random_high(void *buf, size_t nbytes)
+{
+	ssize_t rc;
+
+	if ((rc = read(high_random, buf, nbytes)) == -1)
+		err(EXIT_FAILURE, "/dev/random read failed");
+
+	if (rc < nbytes) {
+		errx(EXIT_FAILURE, "/dev/random read insufficient bytes, "
+		    "%zd instead of %zu.", rc, nbytes);
+	}
+}
+
+void
+random_low(void *buf, size_t nbytes)
+{
+	ssize_t rc;
+
+	if ((rc = read(low_random, buf, nbytes)) == -1)
+		err(EXIT_FAILURE, "/dev/urandom read failed");
+
+	if (rc < nbytes) {
+		errx(EXIT_FAILURE, "/dev/urandom read insufficient bytes, "
+		    "%zd instead of %zu.", rc, nbytes);
+	}
+}
+
+extern uint32_t random_high_32(void);
+extern uint16_t random_high_16(void);
+extern uint8_t random_high_8(void);
+extern uint32_t random_low_32(void);
+extern uint16_t random_low_16(void);
+extern uint8_t random_low_8(void);
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/random.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,71 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2014 Jason King.  All rights reserved.
+ */
+
+#ifndef _RANDOM_H
+#define	_RANDOM_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <inttypes.h>
+
+extern void random_init(void);
+extern void random_high(void *, size_t);
+extern void random_low(void *, size_t);
+extern uint64_t random_high_64(void);
+extern uint64_t random_low_64(void);
+
+inline uint32_t
+random_high_32(void)
+{
+	return ((uint32_t)random_high_64() & (uint64_t)0xffffffff);
+}
+
+inline uint16_t
+random_high_16(void)
+{
+	return ((uint16_t)random_high_64() & (uint64_t)0xffff);
+}
+
+inline uint8_t
+random_high_8(void)
+{
+	return ((uint8_t)random_high_8() & (uint64_t)0xff);
+}
+
+inline uint32_t
+random_low_32(void)
+{
+	return ((uint32_t)random_low_64() & (uint64_t)0xffffffff);
+}
+
+inline uint16_t
+random_low_16(void)
+{
+	return ((uint16_t)random_low_64() & (uint64_t)0xffff);
+}
+
+inline uint8_t
+random_low_8(void)
+{
+	return ((uint8_t)random_low_8() & (uint64_t)0xff);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _RANDOM_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/timer.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,373 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#include <pthread.h>
+#include <errno.h>
+#include <port.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/debug.h>
+#include <umem.h>
+#include <stddef.h>
+#include <locale.h>
+#include <libuutil.h>
+#include <ipsec_util.h>
+#include <note.h>
+#include <ucontext.h>
+
+#include "defs.h"
+#include "timer.h"
+
+typedef struct tevent_s {
+	uu_list_node_t	node;
+
+	hrtime_t	time;	/* When does the event go off */
+	te_event_t	type;	/* Event type */
+
+	tevent_cb_fn	fn;
+	void		*arg;
+} tevent_t;
+
+static uu_list_pool_t 		*timer_pools;
+static pthread_key_t		timer_key = PTHREAD_ONCE_KEY_NP;
+static umem_cache_t		*evt_cache;
+
+static int te_compare(const void *, const void *, void *);
+
+static tevent_t *tevent_alloc(te_event_t, hrtime_t, tevent_cb_fn, void *);
+static void timer_fini(void *);
+static void tevent_free(tevent_t *);
+static int evt_ctor(void *, void *, int);
+static void evt_dtor(void *, void *);
+static const char *te_str(te_event_t);
+
+void
+ike_timer_init(void)
+{
+
+	uint32_t flg = 0;
+
+#ifdef DEBUG
+	flg |= UU_LIST_POOL_DEBUG;
+#endif
+
+	/* better be single threaded here! */
+	ASSERT(pthread_self() == 1);
+
+	timer_pools = uu_list_pool_create("timer_event_list",
+	    sizeof (tevent_t), offsetof(tevent_t, node), te_compare,
+	    flg);
+	if (timer_pools == NULL)
+		errx(EXIT_FAILURE, "Unable to allocate memory for timer event "
+		    "lists");
+
+	evt_cache = umem_cache_create("timer events", sizeof (tevent_t), 0,
+	    evt_ctor, evt_dtor, NULL, NULL, NULL, 0);
+	if (evt_cache == NULL)
+		errx(EXIT_FAILURE, "Unable to allocate memory for timer event "
+		    "entries");
+
+	PTH(pthread_key_create_once_np(&timer_key, timer_fini));
+}
+
+/*
+ * Called for each new worker thread
+ */
+void
+ike_timer_thread_init(void)
+{
+	uu_list_t *list = NULL;
+	uint32_t flg = UU_LIST_SORTED;
+
+#ifdef DEBUG
+	flg |= UU_LIST_DEBUG;
+#endif
+
+	if ((list = uu_list_create(timer_pools, NULL, flg)) == NULL)
+		errx(EXIT_FAILURE, "Unable to allocate timer event lists");
+
+	PTH(pthread_setspecific(timer_key, list));
+}
+
+static int dispatch_cb(void *, void *);
+
+static inline uu_list_t *
+timer_list(void)
+{
+	return (pthread_getspecific(timer_key));
+}
+
+static inline tevent_t *
+timer_head(void)
+{
+	return (uu_list_first(timer_list()));
+}
+
+struct dispatch_arg {
+	bunyan_logger_t *log;
+	hrtime_t now;
+};
+
+void
+process_timer(timespec_t *next_time, bunyan_logger_t *tlog)
+{
+	struct dispatch_arg data = { 0 };
+	tevent_t *te;
+	hrtime_t now;
+
+	ASSERT(timer_is_init);
+	ASSERT(timer_thr_is_init);
+
+	(void) bunyan_trace(tlog, "Checking for timeout events",
+	    BUNYAN_T_END);
+
+	/*CONSTCOND*/
+	while (1) {
+		/*
+		 * since dispatching takes a non-zero amount of time, it is
+		 * possible that by the time we're done dispatching, new
+		 * events are due.  Eventually the list will either drain
+		 * or we are left with an event far enough in the future
+		 * that it's still pending after we're done dispatching
+		 */
+		now = gethrtime();
+
+		if ((te = timer_head()) == NULL) {
+			next_time->tv_sec = 0;
+			next_time->tv_nsec = 0;
+			(void) bunyan_trace(tlog, "Event list empty; returning",
+			    BUNYAN_T_END);
+			return;
+		}
+
+		if (te->time > now) {
+			/* no events to run */
+			hrtime_t delta = te->time - now;
+
+			next_time->tv_sec = NSEC2SEC(delta);
+			next_time->tv_nsec = delta % (hrtime_t)NANOSEC;
+			(void) bunyan_debug(tlog,
+			    "No events ready to dispatch",
+			    BUNYAN_T_UINT32, "events queued",
+			    (uint32_t)uu_list_numnodes(timer_list()),
+			    BUNYAN_T_UINT64, "ns until next event", delta,
+			    BUNYAN_T_END);
+			return;
+		}
+
+		/* dispatch timeouts */
+		data.now = now;
+		data.log = tlog;
+		uu_list_walk(timer_list(), dispatch_cb, &data, UU_WALK_ROBUST);
+	}
+}
+
+static int
+dispatch_cb(void *elem, void *arg)
+{
+	tevent_t *te = (tevent_t *)elem;
+	struct dispatch_arg *data = arg;
+
+	if (te->time > data->now)
+		return (UU_WALK_DONE);
+
+	char buf[128] = { 0 };
+	(void) addrtosymstr(te->fn, buf, sizeof (buf));
+	(void) bunyan_debug(data->log, "Dispatching timer event",
+	    BUNYAN_T_STRING, "event", te_str(te->type),
+	    BUNYAN_T_UINT32, "event num", (uint32_t)te->type,
+	    BUNYAN_T_POINTER, "fn", te->fn,
+	    BUNYAN_T_STRING, "fnname", buf,
+	    BUNYAN_T_POINTER, "arg", te->arg,
+	    BUNYAN_T_END);
+
+	te->fn(te->type, te->arg);
+	uu_list_remove(timer_list(), elem);
+	tevent_free(te);
+
+	return (UU_WALK_NEXT);
+}
+
+typedef struct cancel_arg_s {
+	bunyan_logger_t	*log;
+	te_event_t	type;
+	void		*arg;
+	size_t		n;
+} cancel_arg_t;
+
+static int cancel_cb(void *, void *);
+
+int
+cancel_timeout(te_event_t type, void *arg, bunyan_logger_t *tlog)
+{
+	cancel_arg_t carg;
+
+	ASSERT(timer_is_init);
+	ASSERT(timer_thr_is_init);
+
+	(void) bunyan_trace(tlog, "Cancelling timeouts",
+	    BUNYAN_T_STRING, "event", te_str(type),
+	    BUNYAN_T_UINT32, "event num", (uint32_t)type,
+	    BUNYAN_T_POINTER, "arg", arg,
+	    BUNYAN_T_END);
+
+	carg.log = tlog;
+	carg.type = type;
+	carg.arg = arg;
+	carg.n = 0;
+
+	(void) uu_list_walk(timer_list(), cancel_cb, &carg, UU_WALK_ROBUST);
+	return (carg.n);
+}
+
+static int
+cancel_cb(void *elem, void *arg)
+{
+	tevent_t *te = (tevent_t *)elem;
+	cancel_arg_t *carg = (cancel_arg_t *)arg;
+
+	if (carg->type == TE_ANY ||
+	    ((carg->type == te->type) && (carg->arg == te->arg))) {
+		uu_list_remove(timer_list(), elem);
+
+		(void) bunyan_debug(carg->log, "Removed timeout",
+		    BUNYAN_T_STRING, "event", te_str(te->type),
+		    BUNYAN_T_UINT32, "event num", (uint32_t)te->type,
+		    BUNYAN_T_POINTER, "arg", te->arg,
+		    BUNYAN_T_END);
+
+		tevent_free(te);
+		carg->n++;
+	}
+	return (UU_WALK_NEXT);
+}
+
+boolean_t
+schedule_timeout(te_event_t type, tevent_cb_fn fn, void *arg, hrtime_t val)
+{
+	uu_list_t *list = timer_list();
+	tevent_t *te = tevent_alloc(type, val, fn, arg);
+	uu_list_index_t idx;
+
+	ASSERT(timer_is_init);
+	ASSERT(timer_thr_is_init);
+
+	VERIFY(te != TE_ANY);
+
+	if ((te = tevent_alloc(type, val, fn, arg)) == NULL)
+		return (B_FALSE);
+
+	(void) uu_list_find(list, te, NULL, &idx);
+	uu_list_insert(list, te, idx);
+	return (B_TRUE);
+}
+
+static int
+te_compare(const void *la, const void *ra, void *dummy)
+{
+	NOTE(ARGUNUSED(dummy))
+	const tevent_t *l = (tevent_t *)la;
+	const tevent_t *r = (tevent_t *)ra;
+
+	if (l->time > r->time)
+		return (1);
+	if (l->time < r->time)
+		return (-1);
+	return (0);
+}
+
+static tevent_t *
+tevent_alloc(te_event_t type, hrtime_t dur, tevent_cb_fn fn, void *arg)
+{
+	tevent_t *te = umem_cache_alloc(evt_cache, UMEM_DEFAULT);
+
+	if (te == NULL)
+		return (NULL);
+
+	te->time = gethrtime() + dur;
+	te->type = type;
+	te->fn = fn;
+	te->arg = arg;
+
+	return (te);
+}
+
+static void
+timer_fini(void *arg)
+{
+	uu_list_t *list = arg;
+
+	(void) cancel_timeout(TE_ANY, NULL, log);
+	uu_list_destroy(list);
+}
+
+static void
+tevent_free(tevent_t *te)
+{
+	if (te == NULL)
+		return;
+
+	evt_dtor(te, NULL);
+	evt_ctor(te, NULL, 0);
+	umem_cache_free(evt_cache, te);
+}
+
+static int
+evt_ctor(void *buf, void *cb, int flags)
+{
+	tevent_t *te = (tevent_t *)buf;
+
+	(void) memset(te, 0, sizeof (*te));
+	uu_list_node_init(buf, &te->node, timer_pools);
+	return (0);
+}
+
+static void
+evt_dtor(void *buf, void *cb)
+{
+	tevent_t *te = (tevent_t *)buf;
+
+	uu_list_node_fini(buf, &te->node, timer_pools);
+}
+
+static const char *
+te_str(te_event_t te)
+{
+#define	STR(x) case x: return (#x)
+	switch (te) {
+	STR(TE_TEST);
+	STR(TE_ANY);
+	STR(TE_SA_EXPIRE);
+	STR(TE_COOKIE_GEN);
+	STR(TE_TRANSMIT);
+	STR(TE_PFKEY);
+	default:
+		return ("UNKNOWN");
+	}
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/timer.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,67 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+
+/*
+ * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+ * Use is subject to license terms.
+ *
+ * Copyright 2017 Jason King.
+ * Copyright 2017 Joyent, Inc.
+ */
+/*
+ * Timer events are sorted into a single mutex-protected list.
+ * They are insertion-sorted by next-to-expire.
+ */
+
+#ifndef _TIMER_H
+#define	_TIMER_H
+
+#include <bunyan.h>
+#include <sys/types.h>
+
+#ifdef  __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+	TE_TEST = -1,   /* For code testing only, arg == NULL */
+	TE_ANY,		/* MUST NOT be passed to schedule_timeout() */
+	TE_SA_EXPIRE,   /* SA expiration */
+	TE_COOKIE_GEN,  /* Cookie generation */
+	TE_TRANSMIT,    /* Transmit timeout */
+	TE_PFKEY	/* pfkey timeout */
+} te_event_t;
+
+typedef void (*tevent_cb_fn)(te_event_t, void *);
+
+extern void		process_timer(timespec_t *, bunyan_logger_t *);
+extern int		cancel_timeout(te_event_t, void *, bunyan_logger_t *);
+extern boolean_t	schedule_timeout(te_event_t, tevent_cb_fn, void *,
+    hrtime_t);
+
+extern void ike_timer_init(void);
+extern void ike_timer_thread_init(void);
+
+#ifdef  __cplusplus
+}
+#endif
+
+#endif  /* _TIMER_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/worker.c	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,423 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#include <pthread.h>
+#include <umem.h>
+#include <err.h>
+#include <sys/debug.h>
+#include <bunyan.h>
+#include <time.h>
+#include "defs.h"
+#include "worker.h"
+#include "pkt.h"
+#include "timer.h"
+#include "pkcs11.h"
+#include "ikev2_proto.h"
+
+typedef enum worker_cmd {
+	WC_NONE,
+	WC_SUSPEND,
+	WC_QUIT
+} worker_cmd_t;
+
+typedef struct worker_item {
+	worker_evt_t	wi_event;
+	void		*wi_data;
+} worker_item_t;
+
+typedef struct worker_queue {
+	pthread_mutex_t	wq_lock;
+	pthread_cond_t	wq_cv;
+	worker_cmd_t	wq_cmd;
+	worker_item_t	*wq_items;
+	size_t		wq_start;
+	size_t		wq_end;
+} worker_queue_t;
+#define	WQ_EMPTY(_wq) ((_wq)->wq_start == (_wq)->wq_end)
+#define	WQ_FULL(_wq) ((((_wq)->wq_end + 1) % queuelen) == (_wq)->wq_start)
+
+typedef struct worker {
+	pthread_t	w_tid;
+	bunyan_logger_t	*w_log;
+	worker_queue_t	w_queue;
+	boolean_t	w_done;
+} worker_t;
+
+static pthread_rwlock_t worker_lock = PTHREAD_RWLOCK_INITIALIZER;
+
+size_t	nworkers;
+static worker_t	**workers;
+static size_t	workers_alloc;
+static size_t	queuelen;
+
+static volatile uint_t nsuspended;
+static pthread_mutex_t suspend_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t suspend_cv = PTHREAD_COND_INITIALIZER;
+
+static worker_t *worker_init_one(size_t);
+static void *worker(void *);
+static const char *worker_cmd_str(worker_cmd_t);
+static const char *worker_evt_str(worker_evt_t);
+static void worker_pkt_inbound(pkt_t *);
+
+void
+worker_init(size_t n_workers, size_t queue_sz)
+{
+	size_t len;
+
+	/* to have a gcc with overflow checks... */
+	len = n_workers * sizeof (worker_t *);
+	VERIFY3U(len, >, n_workers);
+	VERIFY3U(len, >, sizeof (worker_t *));
+
+	workers = umem_zalloc(len, UMEM_DEFAULT);
+	if (workers == NULL)
+		err(EXIT_FAILURE, "out of memory");
+
+	nworkers = workers_alloc = n_workers;
+
+	len = queue_sz * sizeof (worker_item_t *);
+	VERIFY3U(len, >, queue_sz);
+	VERIFY3U(len, >, sizeof (worker_item_t *));
+	queuelen = queue_sz;
+
+	for (size_t i = 0; i < nworkers; i++) {
+		worker_t *w = worker_init_one(len);
+
+		if (w == NULL)
+			err(EXIT_FAILURE, "out of memory");
+
+		bunyan_key_add(w->w_log, BUNYAN_T_UINT32, "worker",
+		    (uint32_t)i);
+
+		workers[i] = w;
+	}
+
+	for (size_t i = 0; i < nworkers; i++) {
+		worker_t *w = workers[i];
+		PTH(pthread_create(&w->w_tid, NULL, worker, w));
+	}
+}
+
+static worker_t *
+worker_init_one(size_t len)
+{
+	worker_t *w = NULL;
+
+	if ((w = umem_zalloc(sizeof (*w), UMEM_DEFAULT)) == NULL)
+		return (NULL);
+
+	if ((w->w_queue.wq_items = umem_zalloc(len, UMEM_DEFAULT)) == NULL) {
+		umem_free(w, sizeof (*w));
+		return (NULL);
+	}
+
+	if (bunyan_child(log, &w->w_log, BUNYAN_T_END) != 0)
+		return (NULL);
+
+	PTH(pthread_mutex_init(&w->w_queue.wq_lock, NULL));
+	PTH(pthread_cond_init(&w->w_queue.wq_cv, NULL));
+
+	return (w);
+}
+
+static boolean_t
+worker_send_cmd(size_t n, worker_cmd_t cmd)
+{
+	ASSERT3U(n, <=, nworkers);
+	ASSERT(RW_LOCK_HELD(&worker_lock));
+
+	worker_t *w = workers[n];
+	worker_queue_t *wq = &w->w_queue;
+
+	PTH(pthread_mutex_lock(&wq->wq_lock));
+	if (w->w_queue.wq_cmd != WC_NONE) {
+		PTH(pthread_mutex_unlock(&wq->wq_lock));
+		return (B_FALSE);
+	}
+
+	w->w_queue.wq_cmd = cmd;
+	PTH(pthread_cond_signal(&wq->wq_cv));
+	PTH(pthread_mutex_unlock(&wq->wq_lock));
+
+	return (B_TRUE);
+}
+
+void
+worker_suspend(void)
+{
+	PTH(pthread_rwlock_wrlock(&worker_lock));
+	for (size_t i = 0; i < nworkers; i++) {
+		worker_t *w = workers[i];
+		worker_queue_t *wq = &w->w_queue;
+		PTH(pthread_mutex_lock(&wq->wq_lock));
+		w->w_queue.wq_cmd = WC_SUSPEND;
+		PTH(pthread_cond_signal(&wq->wq_cv));
+		PTH(pthread_mutex_unlock(&wq->wq_lock));
+	}
+	PTH(pthread_rwlock_unlock(&worker_lock));
+
+	PTH(pthread_mutex_lock(&suspend_lock));
+	while (nsuspended != nworkers)
+		PTH(pthread_cond_wait(&suspend_cv, &suspend_lock));
+	PTH(pthread_mutex_unlock(&suspend_lock));
+}
+
+static void
+worker_do_suspend(worker_queue_t *wq)
+{
+	PTH(pthread_mutex_lock(&suspend_lock));
+	if (++nsuspended == nworkers)
+		PTH(pthread_cond_signal(&suspend_cv));
+	PTH(pthread_mutex_unlock(&suspend_lock));
+
+	PTH(pthread_mutex_lock(&wq->wq_lock));
+	while (wq->wq_cmd == WC_SUSPEND)
+		PTH(pthread_cond_wait(&wq->wq_cv, &wq->wq_lock));
+
+	/* leave wq->wq_lock locked */
+}
+
+void
+worker_resume(void)
+{
+	PTH(pthread_rwlock_wrlock(&worker_lock));
+	for (size_t i = 0; i < nworkers; i++) {
+		worker_t *w = workers[i];
+		worker_queue_t *wq = &w->w_queue;
+		PTH(pthread_mutex_lock(&wq->wq_lock));
+		wq->wq_cmd = WC_NONE;
+		PTH(pthread_mutex_unlock(&wq->wq_lock));
+		PTH(pthread_cond_broadcast(&wq->wq_cv));
+	}
+	PTH(pthread_rwlock_unlock(&worker_lock));
+}
+
+boolean_t
+worker_dispatch(worker_evt_t event, void *data, size_t n)
+{
+	worker_t *w = NULL;
+	worker_queue_t *wq = NULL;
+	worker_item_t *wi = NULL;
+
+	PTH(pthread_rwlock_rdlock(&worker_lock));
+	VERIFY3U(n, <, nworkers);
+	w = workers[n];
+	wq = &w->w_queue;
+	PTH(pthread_mutex_lock(&wq->wq_lock));
+
+	if (WQ_FULL(wq)) {
+		PTH(pthread_mutex_unlock(&wq->wq_lock));
+		PTH(pthread_rwlock_unlock(&worker_lock));
+
+		(void) bunyan_debug(log, "dispatch failed (queue full)",
+		    BUNYAN_T_UINT32, "worker", (uint32_t)n,
+		    BUNYAN_T_STRING, "event", worker_evt_str(event),
+		    BUNYAN_T_POINTER, "data", data,
+		    BUNYAN_T_END);
+		return (B_FALSE);
+	}
+
+	wi = &wq->wq_items[wq->wq_end++];
+	wi->wi_event = event;
+	wi->wi_data = data;
+	wq->wq_end %= queuelen;
+
+	PTH(pthread_cond_signal(&wq->wq_cv));
+	PTH(pthread_mutex_unlock(&wq->wq_lock));
+	PTH(pthread_rwlock_unlock(&worker_lock));
+
+	(void) bunyan_debug(w->w_log, "Dispatching packet to worker",
+	    BUNYAN_T_UINT32, "worker", (uint32_t)n,
+	    BUNYAN_T_STRING, "event", worker_evt_str(event),
+	    BUNYAN_T_POINTER, "data", data,
+	    BUNYAN_T_END);
+	return (B_TRUE);
+}
+
+static void *
+worker(void *arg)
+{
+	worker_t *w = arg;
+	worker_queue_t *wq = &w->w_queue;
+	timespec_t ts = { 0 };
+	boolean_t done = B_FALSE;
+
+	ike_timer_thread_init();
+
+	(void) bunyan_trace(w->w_log, "Worker starting", BUNYAN_T_END);
+
+	PTH(pthread_mutex_lock(&wq->wq_lock));
+
+	/*CONSTCOND*/
+	while (1) {
+		process_timer(&ts, w->w_log);
+
+		if (ts.tv_sec == 0 && ts.tv_nsec == 0) {
+			PTH(pthread_cond_wait(&wq->wq_cv, &wq->wq_lock));
+		} else {
+			int rc;
+			rc = pthread_cond_timedwait(&wq->wq_cv, &wq->wq_lock,
+			    &ts);
+			VERIFY(rc == 0 || rc == ETIMEDOUT);
+		}
+
+		if (wq->wq_cmd != WC_NONE)
+			(void) bunyan_info(w->w_log, "Received command",
+			    BUNYAN_T_STRING, "cmd", worker_cmd_str(wq->wq_cmd),
+			    BUNYAN_T_UINT32, "cmdval", (uint32_t)wq->wq_cmd,
+			    BUNYAN_T_END);
+
+		switch (wq->wq_cmd) {
+		case WC_NONE:
+			break;
+		case WC_SUSPEND:
+			worker_do_suspend(wq);
+			ASSERT(MUTEX_HELD(&wq->wq_lock));
+			continue;			
+		case WC_QUIT:
+			done = B_TRUE;
+			break;
+		default:
+			INVALID("wq->wq_cmd");
+		}
+
+		if (done)
+			break;
+
+		while (!WQ_EMPTY(wq)) {
+			worker_item_t wi = wq->wq_items[wq->wq_start];
+
+			wq->wq_items[wq->wq_start].wi_event = EVT_NONE;
+			wq->wq_items[wq->wq_start].wi_data = NULL;
+
+			wq->wq_start++;
+			wq->wq_start %= queuelen;
+			PTH(pthread_mutex_unlock(&wq->wq_lock));
+
+			switch (wi.wi_event) {
+			case EVT_NONE:
+				INVALID("wi.wi_event");
+				break;
+			case EVT_PACKET:
+				worker_pkt_inbound(wi.wi_data);
+				break;
+			case EVT_PFKEY:
+				/* TODO */
+				break;
+			}
+
+			PTH(pthread_mutex_lock(&wq->wq_lock));
+		}
+	}
+
+	w->w_done;
+	PTH(pthread_cond_signal(&wq->wq_cv));
+	PTH(pthread_mutex_unlock(&wq->wq_lock));
+	return (w);
+}
+
+static void
+worker_pkt_inbound(pkt_t *pkt)
+{
+	switch (IKE_GET_MAJORV(pkt->pkt_header.version)) {
+	case 1:
+		/* XXX: ikev1_inbound(pkt); */
+		break;
+	case 2:
+		ikev2_inbound(pkt);
+		break;
+	default:
+		/* XXX: log? */
+		pkt_free(pkt);
+	}
+}
+
+boolean_t
+worker_add(void)
+{
+	worker_t **new_workers = NULL;
+	worker_t *w = NULL;
+	size_t new_workers_alloc = 0;
+	size_t len = 0, qlen = 0;
+
+	(void) bunyan_trace(log, "Creating new worker", BUNYAN_T_END);
+
+	if (workers_alloc == nworkers) {
+		new_workers_alloc = workers_alloc + 1;
+		len = new_workers_alloc * sizeof (worker_t *);
+		VERIFY3U(len, >, new_workers_alloc);
+		VERIFY3U(len, >, sizeof (worker_t *));
+
+		if ((new_workers = umem_zalloc(len, UMEM_DEFAULT)) == NULL)
+			return (B_FALSE);
+
+	} else {
+		new_workers = workers;
+		new_workers_alloc = workers_alloc;
+	}
+
+	qlen = queuelen * sizeof (pkt_t *);
+	VERIFY3U(qlen, >, queuelen);
+	VERIFY3U(qlen, >, sizeof (pkt_t *));
+
+	if ((w = worker_init_one(qlen)) == NULL) {
+		umem_free(new_workers, len);
+		return (B_FALSE);
+	}
+
+	PTH(pthread_create(&w->w_tid, NULL, worker, w));
+
+	PTH(pthread_rwlock_wrlock(&worker_lock));
+	workers = new_workers;
+	workers_alloc = new_workers_alloc;
+
+	VERIFY3U(workers_alloc, >, nworkers);
+	workers[nworkers++] = w;
+
+	PTH(pthread_rwlock_unlock(&worker_lock));
+
+	(void) bunyan_debug(w->w_log, "Worker created", BUNYAN_T_END);
+
+	return (B_TRUE);
+}
+
+void
+worker_del(void)
+{
+}
+
+#define	STR(x) case x: return (#x)
+static const char *
+worker_cmd_str(worker_cmd_t wc)
+{
+	switch (wc) {
+	STR(WC_NONE);
+	STR(WC_SUSPEND);
+	STR(WC_QUIT);
+	}
+	return ("UNKNOWN");
+}
+
+static const char *
+worker_evt_str(worker_evt_t evt)
+{
+	switch (evt) {
+	STR(EVT_NONE);
+	STR(EVT_PACKET);
+	STR(EVT_PFKEY);
+	}
+	return ("UNKNOWN");
+}
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/common/worker.h	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,45 @@
+/*
+ * This file and its contents are supplied under the terms of the
+ * Common Development and Distribution License ("CDDL"), version 1.0.
+ * You may only use this file in accordance with the terms of version
+ * 1.0 of the CDDL.
+ *
+ * A full copy of the text of the CDDL should have accompanied this
+ * source.  A copy of the CDDL is also available via the Internet at
+ * http://www.illumos.org/license/CDDL.
+ */
+
+/*
+ * Copyright 2017 Joyent, Inc.
+ */
+
+#ifndef _WORKER_H
+#define	_WORKER_H
+
+#include <stddef.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum worker_evt {
+	EVT_NONE,
+	EVT_PACKET,
+	EVT_PFKEY
+} worker_evt_t;
+
+extern size_t nworkers;
+
+void worker_init(size_t, size_t);
+void worker_suspend(void);
+void worker_resume(void);
+boolean_t worker_add(void);
+void worker_del(void);
+void worker_stop(void);
+boolean_t worker_dispatch(worker_evt_t, void *, size_t);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* _WORKER_H */
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/i386/Makefile	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,29 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+
+install: all $(ROOTLIBINETPROG32)
--- /dev/null	Wed Aug 23 21:32:34 2017
+++ new/usr/src/cmd/cmd-inet/usr.lib/in.ikev2/sparcv9/Makefile	Wed Aug 23 21:32:34 2017
@@ -0,0 +1,30 @@
+#
+# CDDL HEADER START
+#
+# The contents of this file are subject to the terms of the
+# Common Development and Distribution License (the "License").
+# You may not use this file except in compliance with the License.
+#
+# You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+# or http://www.opensolaris.org/os/licensing.
+# See the License for the specific language governing permissions
+# and limitations under the License.
+#
+# When distributing Covered Code, include this CDDL HEADER in each
+# file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+# If applicable, add the following below this CDDL HEADER, with the
+# fields enclosed by brackets "[]" replaced with your own identifying
+# information: Portions Copyright [yyyy] [name of copyright owner]
+#
+# CDDL HEADER END
+#
+
+#
+# Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
+# Use is subject to license terms.
+#
+
+include ../Makefile.com
+include ../../../../Makefile.cmd.64
+
+install: all $(ROOTLIBINETPROG64)
--- old/usr/src/lib/libipsecutil/common/ikedoor.h	Wed Aug 23 21:32:34 2017
+++ new/usr/src/lib/libipsecutil/common/ikedoor.h	Wed Aug 23 21:32:34 2017
@@ -33,6 +33,7 @@
 #include <sys/sysmacros.h>
 #include <net/pfkeyv2.h>
 #include <door.h>
+#include <sys/socket.h>
 
 /*
  * This version number is intended to stop the calling process from
--- old/usr/src/lib/libipsecutil/common/ipsec_util.h	Wed Aug 23 21:32:34 2017
+++ new/usr/src/lib/libipsecutil/common/ipsec_util.h	Wed Aug 23 21:32:34 2017
@@ -455,6 +455,9 @@
 #define	EXIT_BADPERM2(x, y) \
 	ipsecutil_exit(SERVICE_BADPERM, my_fmri, debugfile, \
 	dgettext(TEXT_DOMAIN, x), y)
+#define	EXIT_BADPERM3(x, y, z) \
+	ipsecutil_exit(SERVICE_BADPERM, my_fmri, debugfile, \
+	dgettext(TEXT_DOMAIN, x), y, z)
 #define	EXIT_FATAL(x) \
 	ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, \
 	dgettext(TEXT_DOMAIN, x))
@@ -464,6 +467,9 @@
 #define	EXIT_FATAL3(x, y, z) \
 	ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, \
 	dgettext(TEXT_DOMAIN, x), y, z)
+#define	EXIT_FATAL4(x, y, z, z2) \
+	ipsecutil_exit(SERVICE_FATAL, my_fmri, debugfile, \
+	dgettext(TEXT_DOMAIN, x), y, z, z2)
 #define	EXIT_RESTART(x) \
 	ipsecutil_exit(SERVICE_RESTART, my_fmri, debugfile, \
 	dgettext(TEXT_DOMAIN, x))
