Merge branch 'lazy-ipv6-resolution' into 'master'

Perform lazy guest address resolution for IPv6

See merge request slirp/libslirp!81
diff --git a/src/ndp_table.c b/src/ndp_table.c
index 61ae8e0..41481ca 100644
--- a/src/ndp_table.c
+++ b/src/ndp_table.c
@@ -39,6 +39,10 @@
 
     /* No entry found, create a new one */
     DEBUG_CALL(" create new entry");
+    /* Save the first entry, it is the guest. */
+    if (in6_zero(&ndp_table->guest_in6_addr)) {
+        ndp_table->guest_in6_addr = ip_addr;
+    }
     ndp_table->table[ndp_table->next_victim].ip_addr = ip_addr;
     memcpy(ndp_table->table[ndp_table->next_victim].eth_addr, ethaddr,
            ETH_ALEN);
diff --git a/src/slirp.c b/src/slirp.c
index ba41843..ee4ce36 100644
--- a/src/slirp.c
+++ b/src/slirp.c
@@ -1192,22 +1192,17 @@
             gaddrlen = sizeof(gdhcp_addr);
         }
     } else {
-        const struct sockaddr_in6 *gaddr_in6 = (const struct sockaddr_in6 *) gaddr;
-
         if (gaddrlen < sizeof(struct sockaddr_in6)) {
             errno = EINVAL;
             return -1;
         }
 
-        if (in6_zero(&gaddr_in6->sin6_addr)) {
-            /*
-             * Libslirp currently only provides a stateless DHCPv6 server, thus
-             * we can't translate "addr-any" to the guest. Instead, for now,
-             * reject it.
-             */
-            errno = EINVAL;
-            return -1;
-        }
+        /*
+         * Libslirp currently only provides a stateless DHCPv6 server, thus
+         * we can't translate "addr-any" to the guest here. Instead, we defer
+         * performing the translation to when it's needed. See
+         * soassign_guest_addr_if_needed().
+         */
     }
 
     if (flags & SLIRP_HOSTFWD_UDP) {
diff --git a/src/slirp.h b/src/slirp.h
index 80f6ac6..e669383 100644
--- a/src/slirp.h
+++ b/src/slirp.h
@@ -113,6 +113,12 @@
 
 typedef struct NdpTable {
     struct ndpentry table[NDP_TABLE_SIZE];
+    /*
+     * The table is a cache with old entries overwritten when the table fills.
+     * Preserve the first entry: it is the guest, which is needed for lazy
+     * hostfwd guest address assignment.
+     */
+    struct in6_addr guest_in6_addr;
     int next_victim;
 } NdpTable;
 
diff --git a/src/socket.c b/src/socket.c
index bf50058..2c1b789 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -680,6 +680,28 @@
              */
             saddr = addr;
             sotranslate_in(so, &saddr);
+
+            /* Perform lazy guest IP address resolution if needed. */
+            if (so->so_state & SS_HOSTFWD) {
+                if (soassign_guest_addr_if_needed(so) < 0) {
+                    DEBUG_MISC(" guest address not available yet");
+                    switch (so->so_lfamily) {
+                    case AF_INET:
+                        icmp_send_error(so->so_m, ICMP_UNREACH,
+                                        ICMP_UNREACH_HOST, 0,
+                                        "guest address not available yet");
+                        break;
+                    case AF_INET6:
+                        icmp6_send_error(so->so_m, ICMP6_UNREACH,
+                                         ICMP6_UNREACH_ADDRESS);
+                        break;
+                    default:
+                        g_assert_not_reached();
+                    }
+                    m_free(m);
+                    return;
+                }
+            }
             daddr = so->lhost.ss;
 
             switch (so->so_ffamily) {
@@ -761,6 +783,15 @@
     DEBUG_ARG("lport = %s", portstr);
     DEBUG_ARG("flags = %x", flags);
 
+    /*
+     * SS_HOSTFWD sockets can be accepted multiple times, so they can't be
+     * SS_FACCEPTONCE. Also, SS_HOSTFWD connections can be accepted and
+     * immediately closed if the guest address isn't available yet, which is
+     * incompatible with the "accept once" concept. Correct code will never
+     * request both, so disallow their combination by assertion.
+     */
+    g_assert(!((flags & SS_HOSTFWD) && (flags & SS_FACCEPTONCE)));
+
     so = socreate(slirp);
 
     /* Don't tcp_attach... we don't need so_snd nor so_rcv */
@@ -1021,3 +1052,53 @@
         s->slirp->cb->notify(s->slirp->opaque);
     }
 }
+
+/*
+ * Translate "addr-any" in so->lhost to the guest's actual address.
+ * Returns 0 for success, or -1 if the guest doesn't have an address yet
+ * with errno set to EHOSTUNREACH.
+ *
+ * The guest address is taken from the first entry in the ARP table for IPv4
+ * and the first entry in the NDP table for IPv6.
+ * Note: The IPv4 path isn't exercised yet as all hostfwd "" guest translations
+ * are handled immediately by using slirp->vdhcp_startaddr.
+ */
+int soassign_guest_addr_if_needed(struct socket *so)
+{
+    Slirp *slirp = so->slirp;
+    /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */
+    char addrstr[INET6_ADDRSTRLEN];
+    char portstr[6];
+
+    g_assert(so->so_state & SS_HOSTFWD);
+
+    switch (so->so_ffamily) {
+    case AF_INET:
+        if (so->so_laddr.s_addr == INADDR_ANY) {
+            g_assert_not_reached();
+        }
+        break;
+
+    case AF_INET6:
+        if (in6_zero(&so->so_laddr6)) {
+            int ret;
+            if (in6_zero(&slirp->ndp_table.guest_in6_addr)) {
+                errno = EHOSTUNREACH;
+                return -1;
+            }
+            so->so_laddr6 = slirp->ndp_table.guest_in6_addr;
+            ret = getnameinfo((const struct sockaddr *) &so->lhost.ss,
+                              sizeof(so->lhost.ss), addrstr, sizeof(addrstr),
+                              portstr, sizeof(portstr),
+                              NI_NUMERICHOST|NI_NUMERICSERV);
+            g_assert(ret == 0);
+            DEBUG_MISC("%s: new ip = [%s]:%s", __func__, addrstr, portstr);
+        }
+        break;
+
+    default:
+        break;
+    }
+
+    return 0;
+}
diff --git a/src/socket.h b/src/socket.h
index 932f391..a73175d 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -181,6 +181,6 @@
 void sotranslate_in(struct socket *, struct sockaddr_storage *);
 void sotranslate_accept(struct socket *);
 void sodrop(struct socket *, int num);
-
+int soassign_guest_addr_if_needed(struct socket *so);
 
 #endif /* SLIRP_SOCKET_H */
diff --git a/src/tcp_subr.c b/src/tcp_subr.c
index b4777ce..600cfa1 100644
--- a/src/tcp_subr.c
+++ b/src/tcp_subr.c
@@ -466,10 +466,41 @@
     struct sockaddr_storage addr;
     socklen_t addrlen = sizeof(struct sockaddr_storage);
     struct tcpcb *tp;
-    int s, opt;
+    int s, opt, ret;
+    /* AF_INET6 addresses are bigger than AF_INET, so this is big enough. */
+    char addrstr[INET6_ADDRSTRLEN];
+    char portstr[6];
 
     DEBUG_CALL("tcp_connect");
     DEBUG_ARG("inso = %p", inso);
+    ret = getnameinfo((const struct sockaddr *) &inso->lhost.ss, sizeof(inso->lhost.ss), addrstr, sizeof(addrstr), portstr, sizeof(portstr), NI_NUMERICHOST|NI_NUMERICSERV);
+    g_assert(ret == 0);
+    DEBUG_ARG("ip = [%s]:%s", addrstr, portstr);
+    DEBUG_ARG("so_state = 0x%x", inso->so_state);
+
+    /* Perform lazy guest IP address resolution if needed. */
+    if (inso->so_state & SS_HOSTFWD) {
+        /*
+         * We can only reject the connection request by accepting it and
+         * then immediately closing it. Note that SS_FACCEPTONCE sockets can't
+         * get here.
+         */
+        if (soassign_guest_addr_if_needed(inso) < 0) {
+            /*
+             * Guest address isn't available yet. We could either try to defer
+             * completing this connection request until the guest address is
+             * available, or punt. It's easier to punt. Otherwise we need to
+             * complicate the mechanism by which we're called to defer calling
+             * us again until the guest address is available.
+             */
+            DEBUG_MISC(" guest address not available yet");
+            s = accept(inso->s, (struct sockaddr *)&addr, &addrlen);
+            if (s >= 0) {
+                close(s);
+            }
+            return;
+        }
+    }
 
     /*
      * If it's an SS_ACCEPTONCE socket, no need to socreate()