diff options
author | Doug Evans <dje@google.com> | 2021-04-07 10:15:42 -0700 |
---|---|---|
committer | Doug Evans <dje@google.com> | 2021-04-12 15:00:35 -0700 |
commit | f8bc26ad23213bd21ff7b540e97216f65969463a (patch) | |
tree | af431d9a9820c28ebc3a33d5215d5a1e1db3fbcc /src/socket.c | |
parent | 4b30c0865b6ee2a15ecfcbd255edbd30be6379f9 (diff) | |
download | slirp-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.c | 81 |
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; +} |