--- old/usr/src/uts/common/Makefile.files	Tue Oct 23 20:42:38 2018
+++ new/usr/src/uts/common/Makefile.files	Tue Oct 23 20:42:38 2018
@@ -1792,7 +1792,8 @@
 	    ip_proxy.o ip_auth.o ip_pool.o ip_htable.o ip_lookup.o \
 	    ip_log.o misc.o ip_compat.o ip_nat6.o drand48.o
 
-VXLNAT_OBJS += vxlnat.o vxlnat_nat.o vxlnat_public_ip.o vxlnat_rules.o
+VXLNAT_OBJS +=	vxlnat.o vxlnat_conn.o vxlnat_nat.o vxlnat_public_ip.o \
+		vxlnat_rules.o
 
 IPD_OBJS += ipd.o
 
--- old/usr/src/uts/common/inet/udp/udp.c	Tue Oct 23 20:42:38 2018
+++ new/usr/src/uts/common/inet/udp/udp.c	Tue Oct 23 20:42:38 2018
@@ -176,8 +176,6 @@
 static void	udp_ud_err(queue_t *q, mblk_t *mp, t_scalar_t err);
 static void	udp_ud_err_connected(conn_t *, t_scalar_t);
 static void	udp_tpi_unbind(queue_t *q, mblk_t *mp);
-static in_port_t udp_update_next_port(udp_t *udp, in_port_t port,
-    boolean_t random);
 static void	udp_wput_other(queue_t *q, mblk_t *mp);
 static void	udp_wput_iocdata(queue_t *q, mblk_t *mp);
 static void	udp_wput_fallback(queue_t *q, mblk_t *mp);
@@ -2653,7 +2651,7 @@
  * us->us_epriv_ports is not sorted thus we loop over it until
  * there are no changes.
  */
-static in_port_t
+in_port_t
 udp_update_next_port(udp_t *udp, in_port_t port, boolean_t random)
 {
 	int i, bump;
--- old/usr/src/uts/common/inet/udp_impl.h	Tue Oct 23 20:42:39 2018
+++ new/usr/src/uts/common/inet/udp_impl.h	Tue Oct 23 20:42:39 2018
@@ -256,6 +256,8 @@
 
 extern void	udp_stack_cpu_add(udp_stack_t *, processorid_t);
 
+extern in_port_t udp_update_next_port(udp_t *, in_port_t, boolean_t);
+
 /*
  * Object to represent database of options to search passed to
  * {sock,tpi}optcom_req() interface routine to take care of option
--- /dev/null	Tue Oct 23 20:42:39 2018
+++ new/usr/src/uts/common/inet/vxlnat/vxlnat_conn.c	Tue Oct 23 20:42:39 2018
@@ -0,0 +1,310 @@
+ /*
+ * 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 2018 Joyent, Inc.
+ */
+
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <inet/ip.h>
+#include <inet/tcp_impl.h>
+#include <inet/udp_impl.h>
+
+#include <inet/vxlnat_impl.h>
+
+/*
+ * Functions for handling conn_t AND for new conn_t receive-side functions
+ * so we can exploit ipclassifier for NAT flows.
+ */
+
+static void
+vxlnat_external_tcp_v4(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_tcp_v6(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_tcp_icmp_v4(void *arg, mblk_t *mp, void *arg2,
+    ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_tcp_icmp_v6(void *arg, mblk_t *mp, void *arg2,
+    ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_udp_v4(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_udp_v6(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_udp_icmp_v4(void *arg, mblk_t *mp, void *arg2,
+    ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_udp_icmp_v6(void *arg, mblk_t *mp, void *arg2,
+    ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_icmp_v4(void *arg, mblk_t *mp, void *arg2, ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+static void
+vxlnat_external_icmp_icmp_v4(void *arg, mblk_t *mp, void *arg2,
+    ip_recv_attr_t *ira)
+{
+	/* XXX KEBE SAYS FOR NOW, drop. */
+	freemsg(mp);
+}
+
+boolean_t
+vxlnat_new_conn(vxlnat_flow_t *flow)
+{
+	conn_t *connp;
+	uint16_t new_lport;
+	uint8_t protocol = flow->vxnfl_protocol;
+	int rc, ntries = 3;
+
+	/*
+	 * XXX KEBE SAYS -- Use KM_NORMALPRI because we're likely in interrupt
+	 * context when we call this function.  If ipcl_conn_create() becomes
+	 * a problem even with these flags, we may need to go asynchronous.
+	 * XXX KEBE ALSO SAYS -- See TCP's handling of new inbound
+	 * connections.
+	 */
+	switch (protocol) {
+	case IPPROTO_TCP:
+	case IPPROTO_UDP:
+	case IPPROTO_ICMP:
+		/* case IPPROTO_ICMP6: */
+		break;
+	default:
+		return (B_FALSE);
+	}
+	connp = ipcl_conn_create(IPCL_IPCCONN, KM_NOSLEEP | KM_NORMALPRI,
+	    vxlnat_netstack);
+	if (connp == NULL)
+		return (B_FALSE);
+
+	/*
+	 * XXX KEBE SAYS FILL IN ALL SORTS OF conn_t STUFF HERE.
+	 * Draw inspiration from iptun_conn_create, but also include
+	 * protocol-specific thingies.
+	 *
+	 * NOTE: As of right this moment, I'm imagining that for
+	 * inside-to-outside, conn_ip_output() will NOT be used, but rather
+	 * ire_forward_recv_v*() will be, not unlike the fixed/1-1 path, and
+	 * that the conn_t's *receive-side* features are the only ones to be
+	 * used.
+	 *
+	 * Also, like UDP, there will be no verifyicmp method assigned.
+	 * (Oddly, iptun does this, but it always returns true. Maybe that's a
+	 * bug in iptun?)
+	 */
+
+	/* connp->conn_flags |= .... */
+	connp->conn_priv = flow;  /* XXX is this a problem for freeing? */
+
+	/*
+	 * XXX KEBE SAYS Don't worry about conn_ixa FOR NOW, but maybe
+	 * fill it in for use later?
+	 */
+
+	/*
+	 * ALWAYS set this to GLOBAL_ZONEID.  We check at open() for
+	 * a non-exclusive zone open (we disallow it), and for exclusive-
+	 * stack zones, we want IP thinking (correctly) we own the netstack.
+	 */
+	connp->conn_zoneid = GLOBAL_ZONEID;
+	/*
+	 * cred_t dance is because we may be getting this straight from
+	 * interrupt context.
+	 */
+	connp->conn_cred = zone_get_kcred(netstack_get_zoneid(vxlnat_netstack));
+	connp->conn_cpid = NOPID;
+
+	ASSERT(connp->conn_ref == 1);
+
+	connp->conn_family = flow->vxnfl_isv4 ? AF_INET : AF_INET6;
+
+	CONN_INC_REF(connp);	/* For the following... */
+	flow->vxnfl_connp = connp;
+
+	/* XXX KEBE SAYS Assume the right thing v4/v6-wise happens for now. */
+	connp->conn_laddr_v6 = flow->vxnfl_rule->vxnr_pubaddr;
+	connp->conn_faddr_v6 = flow->vxnfl_dst;
+
+	/* XXX KEBE SAYS REMAP PORTS HERE ... */
+	connp->conn_ports = flow->vxnfl_ports;
+	connp->conn_proto = protocol;
+
+	/* XXX KEBE ASKS INSERT HERE? */
+	do {
+
+		switch (protocol) {
+		case IPPROTO_TCP: {
+			tcp_stack_t *tcps = vxlnat_netstack->netstack_tcp;
+			tcp_t dummy = {.tcp_tcps = tcps, .tcp_connp = connp};
+
+			/* Fill in with TCP-specific recv/recvicmp. */
+			if (flow->vxnfl_isv4) {
+				connp->conn_recv = vxlnat_external_tcp_v4;
+				connp->conn_recvicmp =
+				    vxlnat_external_tcp_icmp_v4;
+			} else {
+				connp->conn_recv = vxlnat_external_tcp_v6;
+				connp->conn_recvicmp =
+				    vxlnat_external_tcp_icmp_v6;
+			}
+			/* And set new_lport. */
+			new_lport = tcp_update_next_port(
+			    tcps->tcps_next_port_to_try, &dummy, B_TRUE);
+			break;
+		}
+		case IPPROTO_UDP: {
+			udp_stack_t *udps = vxlnat_netstack->netstack_udp;
+			udp_t dummy = {.udp_us = udps, .udp_connp = connp };
+
+			/* Fill in with UDP-specific recv/recvicmp. */
+			if (flow->vxnfl_isv4) {
+				connp->conn_recv = vxlnat_external_udp_v4;
+				connp->conn_recvicmp =
+				    vxlnat_external_udp_icmp_v4;
+			} else {
+				connp->conn_recv = vxlnat_external_udp_v6;
+				connp->conn_recvicmp =
+				    vxlnat_external_udp_icmp_v6;
+			}
+			/* And set new_lport. */
+			new_lport = udp_update_next_port(&dummy,
+			    udps->us_next_port_to_try, B_TRUE);
+			break;
+		}
+		case IPPROTO_ICMP: {
+			/* NOTE:  Only an IPv4 version of this is needed. */
+			connp->conn_recv = vxlnat_external_icmp_v4;
+			connp->conn_recv = vxlnat_external_icmp_icmp_v4;
+			/*
+			 * XXX KEBE SAYS -- I don't think we can tell the real
+			 * IP code to bind an ICMP socket to anything beyond
+			 * the addresses.  But also we allow multiple ICMP
+			 * conn_ts, which could mean duplicate packets.  :-/
+			 */
+			new_lport = 0;
+			break;
+		}
+		default:
+			/* Should never reach here... */
+			cmn_err(CE_PANIC, "vxnfl_protocol corruption!");
+			return (B_FALSE);
+		}
+		connp->conn_lport = new_lport;
+
+		rc = ipcl_conn_insert(connp);
+		switch (rc) {
+		case 0:
+			break;
+		case EADDRINUSE:
+			/* Try rewhacking the ports if we can. */
+			switch (protocol) {
+			case IPPROTO_TCP:
+			case IPPROTO_UDP:
+				/* Try again... */
+				break;
+			default:
+				/* Give up now. */
+				ntries = 1;
+				break;
+			}
+			break;
+		default:
+			/* GET OUT, NOW! */
+			DTRACE_PROBE1(vxlnat__new__conn__badins, int, rc);
+			ntries = 1;
+			break;
+		}
+	} while (rc != 0 && --ntries > 0);
+
+	if (rc != 0) {
+		/* Trash this conn. */
+		DTRACE_PROBE3(vxlnat__new__conn__collision, int, rc,
+		    conn_t *, connp, vxlnat_flow_t *, flow);
+		CONN_DEC_REF(connp);
+		CONN_DEC_REF(connp);
+		/*
+		 * XXX KEBE ASKS Anything else?  Last CONN_DEC_REF should
+		 * trigger destroy.
+		 */
+		flow->vxnfl_connp = NULL;
+		return (B_FALSE);
+	}
+
+	return (B_TRUE);
+}
+
+void
+vxlnat_activate_conn(vxlnat_flow_t *flow)
+{
+	conn_t *connp = flow->vxnfl_connp;
+
+	mutex_enter(&connp->conn_lock);
+	connp->conn_state_flags &= ~CONN_INCIPIENT;
+	mutex_exit(&connp->conn_lock);
+	/* XXX KEBE ASKS OR INSERT HERE? */
+}
+
+#ifdef notyet
+void
+vxlnat_deactivate_conn(vxlnat_flow_t *flow)
+{
+	conn_t *connp = flow->vxnfl_connp;
+
+	ip_quiesce_conn(connp);
+	/* XXX KEBE ASKS ipcl_hash_remove()? */
+}
+#endif
--- old/usr/src/uts/common/inet/vxlnat/vxlnat_nat.c	Tue Oct 23 20:42:39 2018
+++ new/usr/src/uts/common/inet/vxlnat/vxlnat_nat.c	Tue Oct 23 20:42:39 2018
@@ -39,6 +39,7 @@
 #include <netinet/udp.h>
 #include <inet/ip.h>
 #include <inet/ip6.h>
+#include <inet/tcp_impl.h>
 #include <inet/udp_impl.h>
 #include <inet/tcp.h>
 
@@ -349,6 +350,110 @@
 }
 
 /*
+ * Extract transport-level information to find a NAT flow.
+ * Consume mp and return B_FALSE if there's a problem.  Fill in "ports"
+ * and "protocol" and return B_TRUE if there's not.
+ */
+static boolean_t
+vxlnat_grab_transport(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h, uint32_t *ports,
+    uint8_t *protocol, uint8_t **nexthdr_ptr)
+{
+	uint8_t *nexthdr;
+
+	/* Punt on IPv6 for now... */
+	if (ip6h != NULL) {
+		freemsg(mp);
+		return (B_FALSE);
+	}
+
+	ASSERT(ipha != NULL);
+	*protocol = ipha->ipha_protocol;
+	nexthdr = ((uint8_t *)ipha + IPH_HDR_LENGTH(ipha));
+	*nexthdr_ptr = nexthdr;	/* Get this out of the way now. */
+	if (nexthdr > mp->b_wptr) {
+		DTRACE_PROBE1(vxlnat__in__drop__trnexthdr, mblk_t *, mp);
+		freemsg(mp);
+		return (B_FALSE);
+	}
+	switch (*protocol) {
+	case IPPROTO_TCP: {
+		tcpha_t *tcph = (tcpha_t *)nexthdr;
+
+		if (nexthdr + sizeof (*tcph) > mp->b_wptr) {
+			DTRACE_PROBE1(vxlnat__in__drop__tcpnexthdr, mblk_t *,
+			    mp);
+			freemsg(mp);
+			return (B_FALSE);
+		}
+		*ports = *((uint32_t *)tcph);
+		/* XXX KEBE SAYS - grab other metadata here NOW? */
+		break;
+	}
+	case IPPROTO_UDP: {
+		udpha_t *udph = (udpha_t *)nexthdr;
+
+		if (nexthdr + sizeof (*udph) > mp->b_wptr) {
+			DTRACE_PROBE1(vxlnat__in__drop__udpnexthdr, mblk_t *,
+			    mp);
+			freemsg(mp);
+			return (B_FALSE);
+		}
+		*ports = *((uint32_t *)udph);
+		/*
+		 * XXX KEBE SAYS - not as much as TCP, but grab other metadata
+		 * here NOW?
+		 */
+		break;
+	}
+	case IPPROTO_ICMP: {
+		icmph_t *icmph = (icmph_t *)nexthdr;
+
+		if (nexthdr + sizeof (*icmph) > mp->b_wptr) {
+			DTRACE_PROBE1(vxlnat__in__drop__icmpnexthdr, mblk_t *,
+			    mp);
+			freemsg(mp);
+			return (B_FALSE);
+		}
+		/* XXX KEBE SAYS sort out ICMP header... */
+		switch (icmph->icmph_type) {
+		case ICMP_ECHO_REQUEST:
+		case ICMP_TIME_STAMP_REQUEST:
+		case ICMP_TIME_EXCEEDED:
+		case ICMP_INFO_REQUEST:
+		case ICMP_ADDRESS_MASK_REPLY:
+			/* All ones we can sorta cope with... */
+			break;
+		default:
+			DTRACE_PROBE2(vxlnat__in__drop__icmptype, int,
+			    icmph->icmph_type, mblk_t *, mp);
+			freemsg(mp);
+			return (B_FALSE);
+		}
+		/* NOTE: as of now, will switch position depending on endian. */
+		*ports = icmph->icmph_echo_ident;
+		break;
+	}
+	default:
+		*ports = 0;
+		break;
+	}
+
+	return (B_TRUE);
+}
+
+/*
+ * This is the evaluate-packet vs. NAT flow state function.
+ * This function does NOT alter "mp".
+ */
+static boolean_t
+vxlnat_verify_natstate(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h,
+    vxlnat_flow_t *flow, uint8_t *nexthdr)
+{
+	/* XXX KEBE SAYS FILL ME IN! */
+	return (B_FALSE);
+}
+
+/*
  * Inspect the packet and find ports & protos (or ICMP types & codes)
  * and see if we have an established NAT flow.
  *
@@ -362,8 +467,155 @@
 vxlnat_one_vxlan_flow(vxlnat_vnet_t *vnet, mblk_t *mp, ipha_t *ipha,
     ip6_t *ip6h)
 {
-	/* XXX KEBE SAYS FILL ME IN. */
-	/* For now... */
+	vxlnat_flow_t *flow, searcher;
+	uint8_t *nexthdr;
+
+	/*
+	 * XXX KEBE WONDERS, should we return vxlnat_flow_t instead if we
+	 * miss?  That way, we only need to find the ports/protocol ONCE.
+	 */
+
+	if (ip6h != NULL) {
+		/* Eventually, grab addresses for "searcher". */
+		return (B_FALSE);	/* Bail on IPv6 for now... */
+	} else {
+		ASSERT(ipha != NULL);
+		searcher.vxnfl_isv4 = B_TRUE;	/* Required? */
+		IN6_INADDR_TO_V4MAPPED((struct in_addr *)(&ipha->ipha_src),
+		    &searcher.vxnfl_src);
+		IN6_INADDR_TO_V4MAPPED((struct in_addr *)(&ipha->ipha_dst),
+		    &searcher.vxnfl_dst);
+	}
+
+	if (!vxlnat_grab_transport(mp, ipha, ip6h, &searcher.vxnfl_ports,
+	    &searcher.vxnfl_protocol, &nexthdr)) {
+		DTRACE_PROBE1(vxlnat__in__flowgrab, mblk_t *, mp);
+		freemsg(mp);
+		return (B_TRUE);
+	}
+	
+
+	/*
+	 * XXX KEBE SAYS Eventually put the rw&find in an IPv4-only block,
+	 * because IPv6 (if we NAT it like IPv4) will have its own table/tree.
+	 */
+	rw_enter(&vnet->vxnv_flowv4_lock, RW_READER);
+	flow = avl_find(&vnet->vxnv_flows_v4, &searcher, NULL);
+	if (flow != NULL)
+		VXNFL_REFHOLD(flow);
+	rw_exit(&vnet->vxnv_flowv4_lock);
+
+	if (flow == NULL)
+		return (B_FALSE);	/* Let caller handle things. */
+
+	if (!vxlnat_verify_natstate(mp, ipha, ip6h, flow, nexthdr)) {
+		freemsg(mp);	/* XXX KEBE SAYS FOR NOW... */
+	} else {
+		/* XXX KEBE SAYS PROCESS... */
+	}
+
+	VXNFL_REFRELE(flow);
+	return (B_TRUE);
+}
+
+/*
+ * We have a new packet that seems to require a new NAT flow.  Construct that
+ * flow now, and intern it as both a conn_t in IP *and* in the vnet's
+ * appropriate vxnv_flows* tree.  Return NULL if we have a problem.
+ */
+static vxlnat_flow_t *
+vxlnat_new_flow(vxlnat_rule_t *rule, in6_addr_t *inner_src, in6_addr_t *dst,
+    uint32_t ports, uint8_t protocol)
+{
+	vxlnat_vnet_t *vnet = rule->vxnr_vnet;
+	vxlnat_flow_t *flow, *oldflow;
+	avl_tree_t *flowtree;
+	krwlock_t *flowlock;
+	avl_index_t where;
+
+	flow = kmem_alloc(sizeof (*flow), KM_NOSLEEP | KM_NORMALPRI);
+	if (flow == NULL)
+		return (NULL);
+
+	flow->vxnfl_dst = *dst;
+	flow->vxnfl_src = *inner_src;
+	flow->vxnfl_ports = ports;
+	flow->vxnfl_protocol = protocol;
+	flow->vxnfl_refcount = 2; /* One for internment, one for caller. */
+	/* Assume no mixed-IP-version mappings for now. */
+	if (IN6_IS_ADDR_V4MAPPED(inner_src)) {
+		ASSERT(IN6_IS_ADDR_V4MAPPED(dst));
+		flow->vxnfl_isv4 = B_TRUE;
+		flowtree = &vnet->vxnv_flows_v4;
+		flowlock = &vnet->vxnv_flowv4_lock;
+	} else {
+		ASSERT(!IN6_IS_ADDR_V4MAPPED(dst));
+		flow->vxnfl_isv4 = B_FALSE;
+		/* XXX KEBE SAYS we don't do IPv6 for now. */
+		DTRACE_PROBE2(vxlnat__flow__newv6, in6_addr_t *, inner_src,
+		    in6_addr_t *, dst);
+		kmem_free(flow, sizeof (*flow));
+		return (NULL);
+	}
+	VXNR_REFHOLD(rule);	/* For the flow itself... */
+	flow->vxnfl_rule = rule;
+
+	rw_enter(flowlock, RW_WRITER);
+	oldflow = (vxlnat_flow_t *)avl_find(flowtree, flow, &where);
+	if (oldflow != NULL) {
+		/*
+		 * Hmmm, someone put one in while we were dinking around.
+		 * XXX KEBE SAYS return the old one, refheld, for now.
+		 */
+		VXNR_REFRELE(rule);
+		kmem_free(flow, sizeof (*flow));
+		VXNFL_REFHOLD(oldflow);
+		flow = oldflow;
+	} else {
+		avl_insert(flowtree, flow, where);
+		/*
+		 * Do conn_t magic here, except for the conn_t activation.  I
+		 * am aware of holding the rwlock-as-write here.  We may need
+		 * to move this outside the rwlock hold, and
+		 * reacquire-on-failure.
+		 */
+		if (!vxlnat_new_conn(flow)) {
+			ASSERT(flow->vxnfl_connp == NULL);
+			avl_remove(flowtree, flow);
+			VXNR_REFRELE(flow->vxnfl_rule);
+			kmem_free(flow, sizeof (*flow));
+			flow = NULL;
+		}
+	}
+	rw_exit(flowlock);
+	
+	/* We just created this one, activate it. */
+	if (oldflow == NULL && flow != NULL)
+		vxlnat_activate_conn(flow);
+
+	return (flow);
+}
+
+void
+vxlnat_flow_free(vxlnat_flow_t *flow)
+{
+	ASSERT(flow->vxnfl_refcount == 0);
+
+	/* XXX KEBE SAYS FILL ME IN?! */
+	/* XXX KEBE ASKS ipcl_hash_remove()? */
+
+	flow->vxnfl_connp->conn_priv = NULL; /* Sufficient? */
+	CONN_DEC_REF(flow->vxnfl_connp);
+	VXNR_REFRELE(flow->vxnfl_rule);
+	kmem_free(flow, sizeof (*flow));
+}
+
+static boolean_t
+vxlnat_verify_initial(mblk_t *mp, ipha_t *ipha, ip6_t *ip6h,
+    uint32_t ports, uint8_t protocol, uint8_t *nexthdr)
+{
+	/* XXX KEBE SAYS FILL ME IN! */
+	freemsg(mp);
 	return (B_FALSE);
 }
 
@@ -380,12 +632,21 @@
     ip6_t *ip6h)
 {
 	vxlnat_rule_t *rule;
+	vxlnat_flow_t *flow;
+	in6_addr_t v4m_src, v4m_dst, *inner_src, *dst;
+	uint32_t ports;
+	uint8_t protocol;
+	uint8_t *nexthdr;
 
-	/* XXX handle IPv6 later */
+	/* XXX handle IPv6 later, assigning inner_src and dst to ip6_t addrs. */
 	if (ip6h != NULL)
 		return (B_FALSE);
 
 	ASSERT3P(ipha, !=, NULL);
+	inner_src = &v4m_src;
+	dst = &v4m_dst;
+	IN6_INADDR_TO_V4MAPPED((struct in_addr *)(&ipha->ipha_src), inner_src);
+	IN6_INADDR_TO_V4MAPPED((struct in_addr *)(&ipha->ipha_dst), dst);
 
 	mutex_enter(&vnet->vxnv_rule_lock);
 	rule = list_head(&vnet->vxnv_rules);
@@ -393,6 +654,8 @@
 	/*
 	 * search for a match in the nat rules
 	 * XXX investigate perf issues with with respect to list_t size
+	 * XXX KEBE SAYS rewrite when we start doing IPv6 to use "inner_src"
+	 * and "dst". 
 	 */
 	while (rule != NULL) {
 		ipaddr_t ipaddr;
@@ -419,12 +682,38 @@
 		return (B_FALSE);
 
 	/* process packet */
+
 	/*
-	static vxlnat_flow_t *
-	vxlnat_new_flow(vxlnat_rule_t *rule, in6_addr_t *inner_src, in6_addr_t *dst,
-	    uint32_t ports, uint8_t protocol)
+	 * Grab transport header, and figure out if we can proceed.
+	 *
+	 * NOTE: vxlnat_grab_transport() will free/consume mp if it fails,
+	 * because we want to isolate non-flow-starters without having them
+	 * create new flows.  This means we return B_TRUE (consumed mp) on
+	 * failure. 
 	 */
+	if (!vxlnat_grab_transport(mp, ipha, ip6h, &ports, &protocol, &nexthdr))
+		return (B_TRUE); /* see above... */
+	if (!vxlnat_verify_initial(mp, ipha, ip6h, ports, protocol, nexthdr))
+		return (B_TRUE);
+	
 
+	flow = vxlnat_new_flow(rule, inner_src, dst, ports, protocol);
+	if (flow != NULL) {
+		/*
+		 * Call same function that vxlnat_one_vxlan_flow() uses
+		 * to remap & transmit the packet out the external side.
+		 *
+		 * NOTE:  We've already checked the initial-packet-
+		 * qualification, so unlike the main datapath, we don't
+		 * need to call vxlnat_verify_natstate()
+		 */
+
+		 /* XXX KEBE SAYS PROCESS... */
+		
+		VXNFL_REFRELE(flow);
+		return (B_TRUE);
+	}
+
 	return (B_FALSE);
 }
 
@@ -564,12 +853,12 @@
 	    vxlnat_vnet_t, vnet);
 
 	/*
-	 * Off-vxlan processing steps:
+	 * Arrived-from-vxlan processing steps:
 	 * 1.) Locate the ethernet header and check/update/add-into remotes.
 	 * 2.) Search 1-1s, process if hit.
 	 * 3.) Search flows, process if hit.
 	 * 4.) Search rules, create new flow (or not) if hit.
-	 * 5.) Drop the packets.
+	 * 5.) Drop the packet.
 	 */
 
 	/* 1.) Locate the ethernet header and check/update/add-into remotes. */
--- old/usr/src/uts/common/inet/vxlnat_impl.h	Tue Oct 23 20:42:40 2018
+++ new/usr/src/uts/common/inet/vxlnat_impl.h	Tue Oct 23 20:42:40 2018
@@ -51,6 +51,7 @@
 	struct vxlnat_vnet_s *vxnr_vnet;
 	in6_addr_t vxnr_myaddr;
 	in6_addr_t vxnr_pubaddr;
+	/* XXX KEBE ASKS, ire? */
 	uint8_t vxnr_myether[ETHERADDRL];
 	uint16_t vxnr_vlanid;	/* Fabrics use this too. */
 	uint32_t vxnr_refcount;
@@ -71,7 +72,7 @@
 /*
  * NAT FLOWS.  These are per-vnet, and keyed/searched by:
  * <inner-IP-source,IP-dest,inner-source-port,dest-port,protocol>.
- * They will be tied-to/part-of
+ * They will be tied-to/part-of a conn_t.
  */
 typedef struct vxlnat_flow_s {
 	avl_node_t vxnfl_treenode;
@@ -86,8 +87,14 @@
 	uint8_t vxnfl_protocol;
 	uint8_t vxnfl_isv4 : 1,	/* Will save us 12 bytes of compares... */
 		vxlfl_reserved1 : 7;
+	/* Theoretically 16 bits lies where this comment is. */
+	uint32_t vxnfl_refcount;
 	conn_t *vxnfl_connp;	/* Question - embed instead? */
 	vxlnat_rule_t *vxnfl_rule; /* Refhold to rule that generated me. */
+	/*
+	 * XXX KEBE SAYS Other NAT-state belongs here too.  Like time-values
+	 * for timeouts, and more!
+	 */
 } vxlnat_flow_t;
 /* Exploit endianisms, maintain network order... */
 #ifdef _BIG_ENDIAN
@@ -97,6 +104,17 @@
 #define	VXNFL_SPORT(ports) ((ports) & 0xFFFF)
 #define	VXNFL_DPORT(ports) (uint16_t)((ports) >> 16) /* Unsigned all around. */
 #endif
+#define	VXNFL_REFHOLD(vxnfl) {			\
+	atomic_inc_32(&(vxnfl)->vxnfl_refcount);	\
+	ASSERT((vxnfl)->vxnfl_refcount > 0);	\
+}
+#define	VXNFL_REFRELE(vxnfl) {					\
+	ASSERT((vxnfl)->vxnfl_refcount > 0);			\
+	membar_exit();						\
+	if (atomic_dec_32_nv(&(vxnfl)->vxnfl_refcount) == 0)	\
+		vxlnat_flow_free(vxnfl);			\
+}
+extern void vxlnat_flow_free(vxlnat_flow_t *);
 
 /*
  * 1-1 IP mapping.
@@ -263,6 +281,11 @@
 extern int vxlnat_fixed_ire_send_v6(ire_t *, mblk_t *, void *,
     ip_xmit_attr_t *, uint32_t *);
 
+extern boolean_t vxlnat_new_conn(vxlnat_flow_t *);
+extern void vxlnat_activate_conn(vxlnat_flow_t *);
+#ifdef notyet
+extern void vxlnat_deactivate_conn(vxlnat_flow_t *);
+#endif
 
 extern vxlnat_vnet_t *vxlnat_get_vnet(uint32_t, boolean_t);
 
