aboutsummaryrefslogtreecommitdiff
path: root/src/socket.c
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 /src/socket.c
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>
Diffstat (limited to 'src/socket.c')
-rw-r--r--src/socket.c81
1 files changed, 81 insertions, 0 deletions
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;
+}