aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDoug Evans <dje@google.com>2021-04-07 10:15:42 -0700
committerDoug Evans <dje@google.com>2021-04-12 15:00:35 -0700
commitf8bc26ad23213bd21ff7b540e97216f65969463a (patch)
treeaf431d9a9820c28ebc3a33d5215d5a1e1db3fbcc
parent4b30c0865b6ee2a15ecfcbd255edbd30be6379f9 (diff)
downloadslirp-f8bc26ad23213bd21ff7b540e97216f65969463a.zip
slirp-f8bc26ad23213bd21ff7b540e97216f65969463a.tar.gz
slirp-f8bc26ad23213bd21ff7b540e97216f65969463a.tar.bz2
Perform lazy guest address resolution for IPv6
Previously QEMU rejected IPv6 host-forward attempts that had an unspecified guest address. This is because for IPv6 the guest's IP address isn't necessarily known ahead of time: Libslirp only provides a "stateless" DHCPv6 server, which if the macaddr is random then the IPv6 address is random too. This patch changes this to do the address resolution lazily, in the hopes that the guest's IPv6 address is known at the time the user wants to connect to the guest. The request can still fail if the guest doesn't have an IPv6 address yet (e.g., it's still early in the boot). Such requests are immediately rejected. Signed-off-by: Doug Evans <dje@google.com>
-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()