aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/ndp_table.c4
-rw-r--r--src/slirp.c17
-rw-r--r--src/slirp.h6
-rw-r--r--src/socket.c81
-rw-r--r--src/socket.h2
-rw-r--r--src/tcp_subr.c33
6 files changed, 130 insertions, 13 deletions
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 @@ void ndp_table_add(Slirp *slirp, struct in6_addr ip_addr,
/* 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 fcaa179..74f3566 100644
--- a/src/slirp.c
+++ b/src/slirp.c
@@ -1187,22 +1187,17 @@ int slirp_add_hostxfwd(Slirp *slirp,
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 @@ struct ndpentry {
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 2e5fbda..54b695d 100644
--- a/src/socket.c
+++ b/src/socket.c
@@ -680,6 +680,28 @@ void sorecvfrom(struct socket *so)
*/
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 @@ struct socket *tcpx_listen(Slirp *slirp,
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 */
@@ -1020,3 +1051,53 @@ void sodrop(struct socket *s, int num)
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 518aebb..33afb61 100644
--- a/src/socket.h
+++ b/src/socket.h
@@ -180,6 +180,6 @@ int sotranslate_out(struct socket *, struct sockaddr_storage *);
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 @@ void tcp_connect(struct socket *inso)
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()