diff options
-rw-r--r-- | src/ndp_table.c | 4 | ||||
-rw-r--r-- | src/slirp.c | 17 | ||||
-rw-r--r-- | src/slirp.h | 6 | ||||
-rw-r--r-- | src/socket.c | 81 | ||||
-rw-r--r-- | src/socket.h | 2 | ||||
-rw-r--r-- | src/tcp_subr.c | 33 |
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() |