diff --git a/clients/zctl/zctl.c b/clients/zctl/zctl.c
index f18df10a..cc3bb0ce 100644
--- a/clients/zctl/zctl.c
+++ b/clients/zctl/zctl.c
@@ -183,6 +183,7 @@ main(int argc,
 		if (code)
 		    fprintf (stderr, "%s: %s: %s\n",
 			     argv[0], error_message (code), ssline);
+		ZClosePort();
 		exit((code != 0));
 	}
 
@@ -194,6 +195,7 @@ main(int argc,
 #else
 	run_command(argc-1, argv+1);
 #endif
+	ZClosePort();
 	exit(0);
 }
 
diff --git a/clients/zstat/zstat.c b/clients/zstat/zstat.c
index 68568938..f4c95e6b 100644
--- a/clients/zstat/zstat.c
+++ b/clients/zstat/zstat.c
@@ -34,7 +34,9 @@ const char *hm_head[] = {
     "Looking for a new server:",
     "Time running:",
     "Size:",
-    "Machine type:"
+    "Machine type:",
+    "External IP:",
+    "UPnP IGD Root URL:",
 };
 #define	HM_SIZE	(sizeof(hm_head) / sizeof (char *))
 const char *srv_head[] = {
diff --git a/configure.ac b/configure.ac
index dd0dc5aa..2f44fc58 100644
--- a/configure.ac
+++ b/configure.ac
@@ -264,7 +264,12 @@ if test "x$with_ares" != "xno"; then
 			     AC_MSG_ERROR(libcares not found)))
 fi
 AC_SUBST(ARES_LIBS)
-		    
+
+PKG_CHECK_MODULES([UPNP], [miniupnpc], [AC_DEFINE([HAVE_UPNP], [1], [Use UPnP])], [])
+CFLAGS="$UPNP_CFLAGS $CFLAGS"
+LIBS="$UPNP_LIBS $LIBS"
+LIBZEPHYR_LIBS="$LIBZEPHYR_LIBS $UPNP_LIBS"
+
 AC_PROG_GCC_TRADITIONAL
 AC_FUNC_VPRINTF
 AC_FUNC_GETPGRP
diff --git a/h/internal.h b/h/internal.h
index b6e68048..040a6568 100644
--- a/h/internal.h
+++ b/h/internal.h
@@ -115,6 +115,9 @@ extern int __subscriptions_next;
 
 extern int __Zephyr_port;		/* Port number */
 extern struct in_addr __My_addr;
+extern struct in_addr __My_addr_internal;
+extern int __UPnP_active;
+extern char* __UPnP_rooturl;
 extern int __Zephyr_fd;
 extern int __Q_CompleteLength;
 extern struct sockaddr_in __HM_addr;
@@ -205,4 +208,8 @@ Code_t ZFormatAuthenticNotice(ZNotice_t*, char*, int, int*, C_Block);
 #define Z_tktprinc(tkt)		((tkt)->client)
 #endif
 
+void Z_InitUPnP_ZHM();
+void Z_InitUPnP();
+void Z_CloseUPnP();
+
 #endif /* __INTERNAL_H__ */
diff --git a/lib/Makefile.in b/lib/Makefile.in
index d76f2a90..d55f18b1 100644
--- a/lib/Makefile.in
+++ b/lib/Makefile.in
@@ -42,7 +42,7 @@ OBJS =	zephyr_err.lo ZAsyncLocate.lo ZCkAuth.lo ZCkIfNot.lo ZClosePort.lo \
 	ZSendPkt.lo ZSendRaw.lo ZSendRLst.lo ZSetDest.lo ZSetFD.lo ZSetSrv.lo \
 	ZSubs.lo ZVariables.lo ZWait4Not.lo Zinternal.lo ZMakeZcode.lo \
 	ZReadZcode.lo ZCkZAut.lo quad_cksum.lo charset.lo ZExpnRlm.lo \
-	ZDumpSession.lo
+	ZDumpSession.lo ZUPnP.lo
 
 .SUFFIXES: .lo
 
diff --git a/lib/ZClosePort.c b/lib/ZClosePort.c
index bd212b48..849131ea 100644
--- a/lib/ZClosePort.c
+++ b/lib/ZClosePort.c
@@ -19,11 +19,16 @@ static const char rcsid_ZClosePort_c[] = "$Id$";
 Code_t
 ZClosePort(void)
 {
-    if (__Zephyr_fd >= 0 && __Zephyr_open)
-	(void) close(__Zephyr_fd);
+  if (__Zephyr_fd >= 0 && __Zephyr_open) {
+    (void) close(__Zephyr_fd);
 
-    __Zephyr_fd = -1;
-    __Zephyr_open = 0;
+#ifdef Z_DEBUG
+    Z_debug_stderr("ZClosePort() closed port %d", ntohs(__Zephyr_port));
+#endif
+  }
+  Z_CloseUPnP();
+  __Zephyr_fd = -1;
+  __Zephyr_open = 0;
 	
-    return (ZERR_NONE);
+  return (ZERR_NONE);
 }
diff --git a/lib/ZInit.c b/lib/ZInit.c
index abaa2a4f..d2d0620e 100644
--- a/lib/ZInit.c
+++ b/lib/ZInit.c
@@ -17,6 +17,7 @@ static const char rcsid_ZInitialize_c[] =
 
 #include <internal.h>
 
+#include <arpa/inet.h>
 #include <sys/socket.h>
 #ifdef HAVE_KRB4
 #include <krb_err.h>
@@ -24,6 +25,9 @@ static const char rcsid_ZInitialize_c[] =
 #ifdef HAVE_KRB5
 #include <krb5.h>
 #endif
+#if defined(__APPLE__) && defined(__MACH__)
+#include "nwi/network_state_information_priv.h"
+#endif
 
 #ifndef INADDR_NONE
 #define INADDR_NONE 0xffffffff
@@ -55,6 +59,8 @@ ZInitialize(void)
     int s;
     Code_t code;
     ZNotice_t notice;
+    int nf;
+    char* mp;
 #ifdef HAVE_KRB5
     char **krealms = NULL;
 #else
@@ -112,6 +118,8 @@ ZInitialize(void)
        code will fall back to something which might not be "right",
        but this is is ok, since none of the servers call krb_rd_req. */
 
+    __My_addr_internal.s_addr = INADDR_NONE;
+    __My_addr.s_addr = INADDR_NONE;
     servaddr.s_addr = INADDR_NONE;
     if (! __Zephyr_server) {
        if ((code = ZOpenPort(NULL)) != ZERR_NONE)
@@ -143,6 +151,21 @@ ZInitialize(void)
        if (hostent && hostent->h_addrtype == AF_INET)
 	   memcpy(&servaddr, hostent->h_addr, sizeof(servaddr));
 
+       // Field 10 contains our external IP.
+       mp = notice.z_message;
+       *(notice.z_message+notice.z_message_len-1) = 0;
+       for (nf=0; mp<notice.z_message+notice.z_message_len && nf<10; nf++) {
+	 mp += strlen(mp)+1;
+       }
+       if (nf==10 && mp<notice.z_message+notice.z_message_len) {
+	 inet_aton(mp, &__My_addr);
+       }
+       // Field 11 contains the IGD root URL (if UPnP is enabled)
+       mp += strlen(mp)+1;
+       if (mp<notice.z_message+notice.z_message_len) {
+	 __UPnP_rooturl = strdup(mp);
+       }
+
        ZFreeNotice(&notice);
     }
 
@@ -177,7 +200,6 @@ ZInitialize(void)
 #endif
 #endif
 
-    __My_addr.s_addr = INADDR_NONE;
     if (servaddr.s_addr != INADDR_NONE) {
 	/* Try to get the local interface address by connecting a UDP
 	 * socket to the server address and getting the local address.
@@ -192,11 +214,23 @@ ZInitialize(void)
 	    if (connect(s, (struct sockaddr *) &sin, sizeof(sin)) == 0
 		&& getsockname(s, (struct sockaddr *) &sin, &sinsize) == 0
 		&& sin.sin_addr.s_addr != 0)
-		memcpy(&__My_addr, &sin.sin_addr, sizeof(__My_addr));
+		memcpy(&__My_addr_internal, &sin.sin_addr, sizeof(__My_addr_internal));
 	    close(s);
 	}
     }
-    if (__My_addr.s_addr == INADDR_NONE) {
+#if defined(__APPLE__) && defined(__MACH__)
+    if (__My_addr_internal.s_addr == INADDR_NONE) {
+      nwi_state_t state = nwi_state_copy();
+      nwi_ifstate_t ifstate = nwi_state_get_first_ifstate(state, AF_INET);
+      if (ifstate != NULL) {
+	memcpy(&__My_addr_internal, &ifstate->iaddr, sizeof(__My_addr_internal));
+      }
+      if (state != NULL) {
+	nwi_state_release(state);
+      }
+    }
+#endif
+    if (__My_addr_internal.s_addr == INADDR_NONE) {
 	/* We couldn't figure out the local interface address by the
 	 * above method.  Try by resolving the local hostname.  (This
 	 * is a pretty broken thing to do, and unfortunately what we
@@ -204,13 +238,17 @@ ZInitialize(void)
 	if (gethostname(hostname, sizeof(hostname)) == 0) {
 	    hostent = gethostbyname(hostname);
 	    if (hostent && hostent->h_addrtype == AF_INET)
-		memcpy(&__My_addr, hostent->h_addr, sizeof(__My_addr));
+		memcpy(&__My_addr_internal, hostent->h_addr, sizeof(__My_addr_internal));
 	}
     }
     /* If the above methods failed, zero out __My_addr so things will
      * sort of kind of work. */
+    if (__My_addr_internal.s_addr == INADDR_NONE)
+	__My_addr_internal.s_addr = 0;
+
+    /* If ZHM didn't give us an external address, use the internal one */
     if (__My_addr.s_addr == INADDR_NONE)
-	__My_addr.s_addr = 0;
+      __My_addr = __My_addr_internal;
 
     /* Get the sender so we can cache it */
     (void) ZGetSender();
diff --git a/lib/ZLocations.c b/lib/ZLocations.c
index e7537dae..03c26911 100644
--- a/lib/ZLocations.c
+++ b/lib/ZLocations.c
@@ -128,6 +128,8 @@ Z_SendLocation(char *class,
     if (!location_info_set)
 	ZInitLocationInfo(NULL, NULL);
 
+    Z_InitUPnP();
+
     memset((char *)&notice, 0, sizeof(notice));
     notice.z_kind = ACKED;
     notice.z_port = (u_short) ((wg_port == -1) ? 0 : wg_port);
diff --git a/lib/ZOpenPort.c b/lib/ZOpenPort.c
index b15ee286..0b091f6e 100644
--- a/lib/ZOpenPort.c
+++ b/lib/ZOpenPort.c
@@ -66,8 +66,12 @@ ZOpenPort(u_short *port)
     __Zephyr_port = bindin.sin_port;
     __Zephyr_open = 1;
 
+#ifdef Z_DEBUG
+    Z_debug_stderr("ZOpenPort() opened port %d", ntohs(__Zephyr_port));
+#endif
+
     if (port)
-	*port = bindin.sin_port;
+	*port = __Zephyr_port;
 
     return ZERR_NONE;
 }
diff --git a/lib/ZRetSubs.c b/lib/ZRetSubs.c
index 9926171b..5757c0f0 100644
--- a/lib/ZRetSubs.c
+++ b/lib/ZRetSubs.c
@@ -78,6 +78,8 @@ Z_RetSubs(register ZNotice_t *notice,
 		if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE)
 			return (retval);
 
+	Z_InitUPnP();
+
 	notice->z_kind = ACKED;
 	notice->z_port = __Zephyr_port;
 	notice->z_class = ZEPHYR_CTL_CLASS;
diff --git a/lib/ZSubs.c b/lib/ZSubs.c
index 535f3f99..31c797e4 100644
--- a/lib/ZSubs.c
+++ b/lib/ZSubs.c
@@ -93,6 +93,14 @@ ZSubscriptions(register ZSubscription_t *sublist,
     int size, start, numok;
     Z_AuthProc cert_routine;
 
+    if (ZGetFD() < 0) {
+      if ((retval = ZOpenPort((u_short *)0)) != ZERR_NONE) {
+	return (retval);
+      }
+    }
+
+    Z_InitUPnP();
+
     /* nitems = 0 means cancel all subscriptions; still need to allocate a */
     /* array for one item so we can cancel, however. */
   
diff --git a/lib/ZUPnP.c b/lib/ZUPnP.c
new file mode 100644
index 00000000..1a70dc41
--- /dev/null
+++ b/lib/ZUPnP.c
@@ -0,0 +1,85 @@
+#include <internal.h>
+
+#ifdef HAVE_UPNP
+#include <arpa/inet.h>
+#include <miniupnpc/miniupnpc.h>
+#include <miniupnpc/upnpcommands.h>
+
+static struct UPNPUrls __UPnP_urls = {.controlURL = NULL};
+static struct IGDdatas __UPnP_data;
+static int __UPnP_attempted = 0;
+static const char* __UPnP_name = "Zephyr Client";
+
+void Z_InitUPnP_ZHM() {
+  struct UPNPDev * devlist;
+  int upnperror = 0;
+  if (__UPnP_active) {
+    // tried to initialize twice
+    return;
+  }
+  devlist = upnpDiscover(
+			 2000,
+			 NULL/*multicast interface*/,
+			 NULL/*minissdpd socket path*/,
+			 UPNP_LOCAL_PORT_ANY/*sameport*/,
+			 0/*ipv6*/,
+			 2/*TTL*/,
+			 &upnperror);
+  if (devlist) {
+    int igdfound = UPNP_GetValidIGD(devlist, &__UPnP_urls, &__UPnP_data, NULL, 0);
+    if (igdfound) {
+      __UPnP_rooturl = __UPnP_urls.rootdescURL;
+      char extIpAddr[16];
+      if (UPNP_GetExternalIPAddress(__UPnP_urls.controlURL, __UPnP_data.first.servicetype, extIpAddr) == 0) {
+	struct in_addr ext_addr;
+	if (inet_aton(extIpAddr, &ext_addr)) {
+	  __My_addr = ext_addr;
+	  __UPnP_active = 1;
+	}
+      }
+    }
+    freeUPNPDevlist(devlist);
+  }
+  __UPnP_name = "Zephyr Host Manager";
+  Z_InitUPnP();
+}
+
+void Z_InitUPnP() {
+  if (__UPnP_attempted) {
+    return;
+  }
+  __UPnP_attempted = 1;
+  if (__UPnP_rooturl && !__UPnP_active) {
+    __UPnP_active = UPNP_GetIGDFromUrl(__UPnP_rooturl, &__UPnP_urls, &__UPnP_data, NULL, 0);
+  }
+  if (__UPnP_active) {
+    char port_str[16];
+    snprintf(port_str, 16, "%d", ntohs(__Zephyr_port));
+    int ret = UPNP_AddPortMapping(__UPnP_urls.controlURL,
+				  __UPnP_data.first.servicetype,
+				  port_str,
+				  port_str,
+				  inet_ntoa(__My_addr_internal),
+				  __UPnP_name, "UDP", NULL, NULL);
+    // TODO: Handle error 718 (ConflictInMappingEntry) by choosing a new random port.
+  }
+}
+
+void Z_CloseUPnP() {
+  if (__UPnP_active && __UPnP_attempted) {
+    __UPnP_attempted = 0;
+    char port_str[16];
+    snprintf(port_str, 16, "%d", ntohs(__Zephyr_port));
+    UPNP_DeletePortMapping(__UPnP_urls.controlURL,
+			   __UPnP_data.first.servicetype,
+			   port_str,
+			   "UDP",
+			   NULL);
+  }
+}
+#else
+// Noop.
+void Z_InitUPnP_ZHM() {};
+void Z_InitUPnP() {}
+void Z_CloseUPnP() {}
+#endif
diff --git a/lib/Zinternal.c b/lib/Zinternal.c
index ecee122f..c2bb0d33 100644
--- a/lib/Zinternal.c
+++ b/lib/Zinternal.c
@@ -25,7 +25,10 @@ static const char copyright[] =
 int __Zephyr_fd = -1;
 int __Zephyr_open;
 int __Zephyr_port = -1;
-struct in_addr __My_addr;
+struct in_addr __My_addr = {.s_addr = INADDR_NONE};
+struct in_addr __My_addr_internal = {.s_addr = INADDR_NONE};
+int __UPnP_active = 0;
+char* __UPnP_rooturl = NULL;
 int __Q_CompleteLength;
 int __Q_Size;
 struct _Z_InputQ *__Q_Head, *__Q_Tail;
@@ -651,12 +654,12 @@ Z_FormatHeader(ZNotice_t *notice,
     if (!notice->z_sender)
 	notice->z_sender = ZGetSender();
 
+    if (ZGetFD() < 0) {
+      retval = ZOpenPort((u_short *)0);
+      if (retval != ZERR_NONE)
+	return (retval);
+    }
     if (notice->z_port == 0) {
-	if (ZGetFD() < 0) {
-	    retval = ZOpenPort((u_short *)0);
-	    if (retval != ZERR_NONE)
-		return (retval);
-	}
 	notice->z_port = __Zephyr_port;
     }
 
diff --git a/lib/nwi/network_information.h b/lib/nwi/network_information.h
new file mode 100644
index 00000000..3ca3d04f
--- /dev/null
+++ b/lib/nwi/network_information.h
@@ -0,0 +1,286 @@
+/*
+ * Copyright (c) 2011-2019 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+
+#ifndef _NETWORK_INFORMATION_H_
+#define _NETWORK_INFORMATION_H_
+
+#include <os/availability.h>
+#include <stdint.h>
+#include <sys/cdefs.h>
+
+typedef struct _nwi_state * nwi_state_t;
+typedef struct _nwi_ifstate * nwi_ifstate_t;
+
+__BEGIN_DECLS
+
+/*
+ * Function: nwi_state_copy
+ * Purpose:
+ *   Returns the current network state information; NULL if no state
+ *	information is currently available.
+ *   Release after use by calling nwi_state_release().
+ */
+nwi_state_t
+nwi_state_copy(void);
+
+/*
+ * Function: nwi_state_release
+ * Purpose:
+ *   Release the memory associated with the network state.
+ */
+void
+nwi_state_release(nwi_state_t state);
+
+/*
+ * Function: nwi_state_get_notify_key
+ * Purpose:
+ *   Returns the BSD notify key to use to monitor when the state changes.
+ *
+ * Note:
+ *   The nwi_state_copy API uses this notify key to monitor when the state
+ *   changes, so each invocation of nwi_state_copy returns the current
+ *   information.
+ */
+const char *
+nwi_state_get_notify_key(void);
+
+/*
+ * Function: nwi_state_get_first_ifstate
+ * Purpose:
+ *   Returns the first and highest priority interface that has connectivity
+ *   for the specified address family 'af'. 'af' is either AF_INET or AF_INET6.
+ *   The connectivity provided is for general networking.   To get information
+ *   about an interface that isn't available for general networking, use
+ *   nwi_state_get_ifstate().
+ *
+ *   Use nwi_ifstate_get_next() to get the next, lower priority interface
+ *   in the list.
+ *
+ *   Returns NULL if no connectivity for the specified address family is
+ *   available.
+ */
+nwi_ifstate_t
+nwi_state_get_first_ifstate(nwi_state_t state, int af);
+
+/*
+ * Function: nwi_state_get_generation
+ * Purpose:
+ *   Returns the generation of the nwi_state data.
+ *   Every time the data is updated due to changes
+ *   in the network, this value will change.
+ */
+uint64_t
+nwi_state_get_generation(nwi_state_t state);
+
+/*
+ * Function: nwi_ifstate_get_generation
+ * Purpose:
+ *   Returns the generation of the nwi_ifstate data.
+ */
+uint64_t
+nwi_ifstate_get_generation(nwi_ifstate_t ifstate);
+
+/*
+ * Function: nwi_state_get_ifstate
+ * Purpose:
+ *   Return information for the specified interface 'ifname'.
+ *
+ *   This API directly returns the ifstate for the specified interface.
+ *   This is the only way to access information about an interface that isn't
+ *   available for general networking.
+ *
+ *   Returns NULL if no information is available for that interface.
+ */
+nwi_ifstate_t
+nwi_state_get_ifstate(nwi_state_t state, const char * ifname);
+
+/*
+ * Function: nwi_ifstate_get_ifname
+ * Purpose:
+ *   Return the interface name of the specified ifstate.
+ */
+const char *
+nwi_ifstate_get_ifname(nwi_ifstate_t ifstate);
+
+/*
+ * Type: nwi_ifstate_flags
+ * Purpose:
+ *   Provide information about the interface, including its IPv4 and IPv6
+ *   connectivity, and whether DNS is configured or not.
+ */
+#define NWI_IFSTATE_FLAGS_HAS_IPV4	0x1	/* has IPv4 connectivity */
+#define NWI_IFSTATE_FLAGS_HAS_IPV6	0x2	/* has IPv6 connectivity */
+#define NWI_IFSTATE_FLAGS_HAS_DNS	0x4	/* has DNS configured */
+#define NWI_IFSTATE_FLAGS_HAS_CLAT46	0x0040	/* has CLAT46 configured */
+
+typedef uint64_t nwi_ifstate_flags;
+/*
+ * Function: nwi_ifstate_get_flags
+ * Purpose:
+ *   Return the flags for the given ifstate (see above for bit definitions).
+ */
+nwi_ifstate_flags
+nwi_ifstate_get_flags(nwi_ifstate_t ifstate);
+
+/*
+ * Function: nwi_ifstate_get_next
+ * Purpose:
+ *   Returns the next, lower priority nwi_ifstate_t after the specified
+ *   'ifstate' for the protocol family 'af'.
+ *
+ *   Returns NULL when the end of the list is reached.
+ */
+nwi_ifstate_t
+nwi_ifstate_get_next(nwi_ifstate_t ifstate, int af);
+
+/*
+ * Function: nwi_ifstate_compare_rank
+ * Purpose:
+ *   Compare the relative rank of two nwi_ifstate_t objects.
+ *
+ *   The "rank" indicates the importance of the underlying interface.
+ *
+ * Returns:
+ *   0 	if ifstate1 and ifstate2 are ranked equally
+ *  -1	if ifstate1 is ranked ahead of ifstate2
+ *   1	if ifstate2 is ranked ahead of ifstate1
+ */
+int
+nwi_ifstate_compare_rank(nwi_ifstate_t ifstate1, nwi_ifstate_t ifstate2);
+
+/*
+ * Function: _nwi_state_ack
+ * Purpose:
+ *   Acknowledge receipt and any changes associated with the [new or
+ *   updated] network state.
+ */
+void
+_nwi_state_ack(nwi_state_t state, const char *bundle_id)
+	API_AVAILABLE(macos(10.8), ios(6.0));
+
+/*
+ * Function: nwi_state_get_reachability_flags
+ * Purpose:
+ * Returns the global reachability flags for a given address family.
+ * If no address family is passed in, it returns the global reachability
+ * flags for either families.
+ *
+ * The reachability flags returned follow the definition of
+ * SCNetworkReachabilityFlags.
+ *
+ * If the flags are zero (i.e. do not contain kSCNetworkReachabilityFlagsReachable), there is no connectivity.
+ *
+ * Otherwise, at least kSCNetworkReachabilityFlagsReachable is set:
+ *        Reachable only
+ *          No other connection flags are set.
+ *        Reachable and no ConnectionRequired
+ *          If we have connectivity for the specified address family (and we'd
+ *          be returning the reachability flags associated with the default route)
+ *        Reachable and ConnectionRequired
+ *          If we do not currently have an active/primary network but we may
+ *          be able to establish connectivity.
+ *        Reachable and OnDemand
+ *          If we do not currently have an active/primary network but we may
+ *          be able to establish connective on demand.
+ *        Reachable and TransientConnection
+ *          This connection is transient.
+ *        Reachable and WWAN
+ *          This connection will be going over the cellular network.
+ */
+uint32_t
+nwi_state_get_reachability_flags(nwi_state_t nwi_state, int af);
+
+/*
+ * Function: nwi_state_get_interface_names
+ * Purpose:
+ *   Returns the list of network interface names that have connectivity.
+ *   The list is sorted from highest priority to least, highest priority
+ *   appearing at index 0.
+ *
+ *   If 'names' is NULL or 'names_count' is zero, this function returns
+ *   the number of elements that 'names' must contain to get the complete
+ *   list of interface names.
+ *
+ *   If 'names' is not NULL and 'names_count' is not zero, fills 'names' with
+ *   the list of interface names not exceeding 'names_count'. Returns the
+ *   number of elements that were actually populated.
+ *
+ * Notes:
+ * 1. The connectivity that an interface in this list provides may not be for
+ *    general purpose use.
+ * 2. The string pointers remain valid only as long as 'state' remains
+ *    valid.
+ */
+unsigned int
+nwi_state_get_interface_names(nwi_state_t state,
+			      const char * names[],
+			      unsigned int names_count);
+
+/*
+ * nwi_ifstate_get_vpn_server
+ *
+ * returns a sockaddr representation of the vpn server address.
+ * NULL if PPP/VPN/IPSec server address does not exist.
+ */
+const struct sockaddr *
+nwi_ifstate_get_vpn_server(nwi_ifstate_t ifstate);
+
+/*
+ * nwi_ifstate_get_reachability_flags
+ *
+ * returns the reachability flags for the interface given an address family.
+ * The flags returned are those determined outside of
+ * the routing table.  [None, ConnectionRequired, OnDemand,
+ * Transient Connection, WWAN].
+ */
+uint32_t
+nwi_ifstate_get_reachability_flags(nwi_ifstate_t ifstate);
+
+/*
+ * nwi_ifstate_get_signature
+ *
+ * returns the signature and its length for an ifstate given an address family.
+ * If AF_UNSPEC is passed in, the signature for a given ifstate is returned.
+ *
+ * If the signature does not exist, NULL is returned.
+ */
+const uint8_t *
+nwi_ifstate_get_signature(nwi_ifstate_t ifstate, int af, int * length);
+
+
+/*
+ * nwi_ifstate_get_dns_signature
+ *
+ * returns the signature and its length for given
+ * ifstate with a valid dns configuration.
+ *
+ * If the signature does not exist, NULL is returned.
+ *
+ */
+const uint8_t *
+nwi_ifstate_get_dns_signature(nwi_ifstate_t ifstate, int * length);
+
+__END_DECLS
+
+#endif
diff --git a/lib/nwi/network_state_information_priv.h b/lib/nwi/network_state_information_priv.h
new file mode 100644
index 00000000..06d31c4e
--- /dev/null
+++ b/lib/nwi/network_state_information_priv.h
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2011-2013, 2016-2019 Apple Inc. All rights reserved.
+ *
+ * @APPLE_LICENSE_HEADER_START@
+ *
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this
+ * file.
+ *
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ *
+ * @APPLE_LICENSE_HEADER_END@
+ */
+
+#ifndef _NETWORK_STATE_INFORMATION_PRIV_H_
+#define _NETWORK_STATE_INFORMATION_PRIV_H_
+
+#include <CommonCrypto/CommonDigest.h>
+#include <net/if.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+#include <netinet/in.h>
+#include <sys/socket.h>
+
+#include "network_information.h"
+
+#define NWI_STATE_VERSION	((uint32_t)0x20170601)
+
+
+#define NWI_IFSTATE_FLAGS_NOT_IN_LIST	0x0008
+#define NWI_IFSTATE_FLAGS_HAS_SIGNATURE	0x0010
+#define NWI_IFSTATE_FLAGS_NOT_IN_IFLIST	0x0020
+
+/*
+ * NWI_IFSTATE_FLAGS_MASK
+ * - these are the bits that get preserved, all others are
+ *   control (last item, diff)
+ */
+#define NWI_IFSTATE_FLAGS_MASK		0x00ff
+
+
+#define NWI_IFSTATE_FLAGS_DIFF_MASK	0x0f00
+#define NWI_IFSTATE_FLAGS_LAST_ITEM	0x1000
+
+typedef enum {
+    knwi_ifstate_difference_none = 0,
+    knwi_ifstate_difference_changed = 1,
+    knwi_ifstate_difference_removed = 2
+} nwi_ifstate_difference_t;
+
+
+/*
+ * Type: Rank
+ * Purpose:
+ *   A 32-bit value to encode the relative rank of a service.
+ *
+ *   The top 8 bits are used to hold the rank assertion (first, default, last,
+ *   never, scoped);
+ *
+ *   The bottom 24 bits are used to store the service index (i.e. the
+ *   position within the service order array).
+ */
+typedef uint32_t        Rank;
+#define RANK_ASSERTION_MAKE(r)		((Rank)(r) << 24)		// rank assertion (top 8 bits)
+#define kRankAssertionFirst		RANK_ASSERTION_MAKE(0)
+#define kRankAssertionDefault		RANK_ASSERTION_MAKE(1)
+#define kRankAssertionLast		RANK_ASSERTION_MAKE(2)
+#define kRankAssertionNever		RANK_ASSERTION_MAKE(3)
+#define kRankAssertionScoped		RANK_ASSERTION_MAKE(4)
+#define kRankAssertionMask		RANK_ASSERTION_MAKE(0xff)
+#define RANK_ASSERTION_MASK(r)		((Rank)(r) & kRankAssertionMask)
+#define RANK_INDEX_MAKE(r)		((Rank)(r))			// rank index (bottom 24 bits)
+#define kRankIndexMask			RANK_INDEX_MAKE(0xffffff)
+#define RANK_INDEX_MASK(r)		((Rank)(r) & kRankIndexMask)
+
+typedef int32_t		nwi_ifindex_t;
+
+#define NWI_SIGNATURE_LENGTH	20
+
+#pragma pack(4)
+typedef struct _nwi_ifstate {
+	char			ifname[IFNAMSIZ];
+	uint64_t		flags;
+	nwi_ifindex_t		af_alias_offset; /* relative index to alias */
+	Rank			rank;
+	sa_family_t		af;
+	union {
+	    struct in_addr	iaddr;
+	    struct in6_addr	iaddr6;
+	};
+	uint64_t		if_generation_count;
+	uint32_t		reach_flags;
+	union {
+	    struct sockaddr_in	vpn_server_address4;
+	    struct sockaddr_in6	vpn_server_address6;
+	} vpn_server_address;
+	unsigned char		signature[NWI_SIGNATURE_LENGTH];
+} nwi_ifstate;
+#pragma pack()
+
+#pragma pack(4)
+typedef struct _nwi_state {
+	uint32_t	version;	/* NWI_STATE_VERSION */
+	nwi_ifindex_t	max_if_count;	/* available slots per protocol */
+	nwi_ifindex_t	ipv4_count;	/* # of v4 ifstates in use */
+	nwi_ifindex_t	ipv6_count;	/* # of v6 ifstates in use */
+	nwi_ifindex_t	if_list_count;	/* # of if_list[] slots in use */
+	uint32_t	ref;		/* reference count */
+	uint32_t	reach_flags_v4;
+	uint32_t	reach_flags_v6;
+	uint64_t	generation_count;
+	nwi_ifstate	ifstate_list[1];/* (max_if_count * 2) ifstates */
+/*	nwi_ifindex_t 	if_list[0];        max_if_count indices */
+} nwi_state;
+#pragma pack()
+
+static __inline__ int
+nwi_other_af(int af)
+{
+	return ((af == AF_INET) ? (AF_INET6) : (AF_INET));
+}
+
+static __inline__ size_t
+nwi_state_compute_size(unsigned int max_if_count)
+{
+	size_t	size;
+
+	size = offsetof(nwi_state, ifstate_list[max_if_count * 2])
+		+ sizeof(nwi_ifindex_t) * max_if_count;
+	return (size);
+}
+
+static __inline__ size_t
+nwi_state_size(nwi_state_t state)
+{
+	return (nwi_state_compute_size(state->max_if_count));
+}
+
+static __inline__ nwi_ifstate_t
+nwi_state_ifstate_list(nwi_state_t state, int af)
+{
+	if (af == AF_INET) {
+		return (state->ifstate_list);
+	}
+	return (state->ifstate_list + state->max_if_count);
+}
+
+static __inline__ nwi_ifindex_t *
+nwi_state_if_list(nwi_state_t state)
+{
+	return ((nwi_ifindex_t *)&state->ifstate_list[state->max_if_count * 2]);
+}
+
+static __inline__ int
+uint32_cmp(uint32_t a, uint32_t b)
+{
+	int		ret;
+
+	if (a == b) {
+		ret = 0;
+	}
+	else if (a < b) {
+		ret = -1;
+	}
+	else {
+		ret = 1;
+	}
+	return (ret);
+}
+
+static __inline__ int
+RankCompare(Rank a, Rank b)
+{
+	return (uint32_cmp(a, b));
+}
+
+/*
+ * Function: nwi_state_get_ifstate_count
+ * Purpose:
+ *   Return the number of ifstate elements for the specified address family
+ *   'af'. 'af' is either AF_INET or AF_INET6.
+ *
+ *   Returns zero if there are no elements.
+ */
+static __inline__
+int
+nwi_state_get_ifstate_count(nwi_state_t state, int af)
+{
+	return (af == AF_INET)?state->ipv4_count:state->ipv6_count;
+}
+
+static __inline__ nwi_ifstate_t
+nwi_ifstate_get_alias(nwi_ifstate_t ifstate, int af)
+{
+	if (ifstate->af == af) {
+		return (ifstate);
+	}
+	if (ifstate->af_alias_offset == 0) {
+		return (NULL);
+	}
+	return (ifstate + ifstate->af_alias_offset);
+}
+
+/*
+ *   The ifstate list is sorted in order of decreasing priority, with the
+ *   highest priority element appearing at index zero.
+ *
+ *   If 'idx' is outside of the bounds of the corresponding array, returns NULL.
+ */
+static __inline__
+nwi_ifstate_t
+nwi_state_get_ifstate_with_index(nwi_state_t state, int af, int idx)
+{
+	int i_idx = idx;
+
+	if (idx >= nwi_state_get_ifstate_count(state, af)) {
+		return (NULL);
+	}
+
+	if (af == AF_INET6) {
+		i_idx = idx + state->max_if_count;
+	}
+
+	return &state->ifstate_list[i_idx];
+}
+
+/*
+ * Function: nwi_state_get_ifstate_with_name
+ * Purpose:
+ *   Return the ifstate for the specified ifstate for the specified address
+ *   family 'af'. 'af' is either AF_INET or AF_INET6.
+ *
+ *   Returns NULL if no such information exists.
+ */
+static __inline__
+nwi_ifstate_t
+nwi_state_get_ifstate_with_name(nwi_state_t state,
+				 int af, const char * name)
+{
+	int idx = 0;
+	int count;
+	nwi_ifstate_t ifstate = NULL;
+
+	if (state == NULL) {
+		return NULL;
+	}
+
+	count = (af == AF_INET)
+	?state->ipv4_count:state->ipv6_count;
+
+
+	while (idx < count) {
+		ifstate = nwi_state_get_ifstate_with_index(state, af, idx);
+		if (ifstate == NULL) {
+			break;
+		}
+		if (strcmp(name,
+			   nwi_ifstate_get_ifname(ifstate)) == 0) {
+			return (ifstate);
+		}
+		idx++;
+	}
+	return (NULL);
+}
+
+static __inline__
+void
+_nwi_ifstate_set_vpn_server(nwi_ifstate_t ifstate, struct sockaddr *serv_addr)
+{
+	size_t len;
+
+	if (serv_addr == NULL) {
+		memset(&ifstate->vpn_server_address, 0, sizeof(ifstate->vpn_server_address));
+		return;
+	}
+
+	len = serv_addr->sa_len;
+
+	if (len == 0 || len > sizeof(ifstate->vpn_server_address)) {
+		return;
+	}
+
+	memcpy(&ifstate->vpn_server_address,
+	       serv_addr,
+	       len);
+	return;
+
+}
+
+static __inline__
+void
+_nwi_state_set_reachability_flags(nwi_state_t state, uint32_t reach_flags_v4, uint32_t reach_flags_v6)
+{
+	state->reach_flags_v4 = reach_flags_v4;
+	state->reach_flags_v6 = reach_flags_v6;
+	return;
+}
+
+nwi_state_t
+nwi_state_new(nwi_state_t old_state, int elems);
+
+nwi_state_t
+nwi_state_make_copy(nwi_state_t state);
+
+static __inline__ void
+nwi_state_free(nwi_state_t state)
+{
+	free(state);
+	return;
+}
+
+void
+nwi_state_finalize(nwi_state_t state);
+
+nwi_ifstate_t
+nwi_state_add_ifstate(nwi_state_t state, const char* ifname, int af,
+		      uint64_t flags, Rank rank,
+		      void * ifa, struct sockaddr * vpn_server_addr, uint32_t reach_flags);
+
+void
+nwi_ifstate_set_signature(nwi_ifstate_t ifstate, uint8_t * signature);
+
+void
+nwi_state_clear(nwi_state_t state, int af);
+
+nwi_state_t
+nwi_state_diff(nwi_state_t old_state, nwi_state_t new_state);
+
+void *
+nwi_ifstate_get_address(nwi_ifstate_t ifstate);
+
+const char *
+nwi_ifstate_get_diff_str(nwi_ifstate_t ifstate);
+
+nwi_ifstate_difference_t
+nwi_ifstate_get_difference(nwi_ifstate_t diff_ifstate);
+
+void
+_nwi_state_update_interface_generations(nwi_state_t old_state, nwi_state_t state, nwi_state_t changes);
+
+void
+_nwi_state_compute_sha256_hash(nwi_state_t state,
+			       unsigned char hash[CC_SHA256_DIGEST_LENGTH]);
+
+#endif	// _NETWORK_STATE_INFORMATION_PRIV_H_
diff --git a/zhm/Makefile.in b/zhm/Makefile.in
index 77bf2c0a..0f1f914c 100644
--- a/zhm/Makefile.in
+++ b/zhm/Makefile.in
@@ -32,6 +32,7 @@ CPPFLAGS=@CPPFLAGS@
 CFLAGS=@CFLAGS@
 ALL_CFLAGS=${CFLAGS} -I${top_srcdir}/h -I${BUILDTOP}/h ${CPPFLAGS}
 LDFLAGS=@LDFLAGS@
+LIBS=@LIBS@
 HESIOD_LIBS=@HESIOD_LIBS@
 
 OBJS=	timer.o queue.o zhm.o zhm_client.o zhm_server.o
@@ -39,7 +40,7 @@ OBJS=	timer.o queue.o zhm.o zhm_client.o zhm_server.o
 all: zhm zhm.8
 
 zhm: ${OBJS} ${LIBZEPHYR}
-	${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${HESIOD_LIBS} -lcom_err
+	${LIBTOOL} --mode=link ${CC} ${LDFLAGS} -o $@ ${OBJS} ${LIBZEPHYR} ${LIBS} ${HESIOD_LIBS} -lcom_err
 
 zhm.8: ${srcdir}/zhm.8.in Makefile
 	${editman} ${srcdir}/$@.in > $@.tmp
diff --git a/zhm/zhm.c b/zhm/zhm.c
index ec4696b7..d0bd0d11 100644
--- a/zhm/zhm.c
+++ b/zhm/zhm.c
@@ -407,6 +407,8 @@ init_hm(void)
      }
      cli_sin = ZGetDestAddr();
 
+     Z_InitUPnP_ZHM();
+
      sp = getservbyname(SERVER_SVCNAME, "udp");
      memset(&serv_sin, 0, sizeof(struct sockaddr_in));
      serv_sin.sin_port = (sp) ? sp->s_port : SERVER_SVC_FALLBACK;
@@ -527,7 +529,7 @@ send_stats(ZNotice_t *notice,
      Code_t ret;
      char *bfr;
      char *list[20];
-     int len, i, nitems = 10;
+     int len, i, nitems = 11;
      unsigned long size;
      extern int Zauthtype; /* XXX this may be changing in the future */
 
@@ -580,6 +582,16 @@ send_stats(ZNotice_t *notice,
      strncpy(list[9], MACHINE_TYPE, 32);
      list[9][31] = '\0';
 
+     list[10] = stats_malloc(32);
+     strncpy(list[10], inet_ntoa(__My_addr), 32);
+     list[10][31] = '\0';
+
+     if (__UPnP_rooturl) {
+       list[11] = stats_malloc(strlen(__UPnP_rooturl));
+       strcpy(list[11], __UPnP_rooturl);
+       nitems++;
+     }
+
      /* Since ZFormatRaw* won't change the version number on notices,
 	we need to set the version number explicitly.  This code is taken
 	from Zinternal.c, function Z_FormatHeader */