@@ -1322,7 +1322,8 @@ ERST
{
.name = "hostfwd_add",
.args_type = "arg1:s,arg2:s?",
- .params = "[netdev_id] [tcp|udp]:[hostaddr]:hostport-[guestaddr]:guestport",
+ .params = "[netdev_id] [tcp|udp]:[hostaddr]:hostport\n"
+ "[,ipv4=on|off][,ipv6=on|off]-[guestaddr]:guestport",
.help = "redirect TCP or UDP connections from host to guest (requires -net user)",
.cmd = hmp_hostfwd_add,
},
@@ -1330,13 +1331,20 @@ ERST
SRST
``hostfwd_add``
Redirect TCP or UDP connections from host to guest (requires -net user).
+ IPV6 addresses are wrapped in square brackets, IPV4 addresses are not.
+
+ Examples:
+ hostfwd_add net0 tcp:127.0.0.1:10022-:22
+ hostfwd_add net0 tcp:[::1]:10022-[fe80::1:2:3:4]:22
+ hostfwd_add net0 ::10022,ipv6-:22
ERST
#ifdef CONFIG_SLIRP
{
.name = "hostfwd_remove",
.args_type = "arg1:s,arg2:s?",
- .params = "[netdev_id] [tcp|udp]:[hostaddr]:hostport",
+ .params = "[netdev_id] [tcp|udp]:[hostaddr]:hostport\n"
+ "[,ipv4=on|off][,ipv6=on|off]",
.help = "remove host-to-guest TCP or UDP redirection",
.cmd = hmp_hostfwd_remove,
},
@@ -1345,6 +1353,12 @@ ERST
SRST
``hostfwd_remove``
Remove host-to-guest TCP or UDP redirection.
+ IPV6 addresses are wrapped in square brackets, IPV4 addresses are not.
+
+ Examples:
+ hostfwd_remove net0 tcp:127.0.0.1:10022
+ hostfwd_remove net0 tcp:[::1]:10022
+ hostfwd_remove net0 ::10022,ipv6
ERST
{
@@ -664,25 +664,55 @@ static const char *parse_protocol(const char *str, bool *is_udp,
return p;
}
-static int parse_hostfwd_sockaddr(const char *str, int socktype,
+static int parse_hostfwd_sockaddr(const char *str, int family, int socktype,
struct sockaddr_storage *saddr,
- Error **errp)
+ bool *v6_only, Error **errp)
{
struct addrinfo hints, *res = NULL, *e;
InetSocketAddress *addr = g_new(InetSocketAddress, 1);
int gai_rc;
int rc = -1;
+ Error *err = NULL;
const char *optstr = inet_parse_host_port(addr, str, errp);
if (optstr == NULL) {
goto fail_return;
}
+ if (inet_parse_ipv46(addr, optstr, errp) < 0) {
+ goto fail_return;
+ }
+
+ if (v6_only) {
+ bool v4 = addr->has_ipv4 && addr->ipv4;
+ bool v6 = addr->has_ipv6 && addr->ipv6;
+ *v6_only = v6 && !v4;
+ }
+
memset(&hints, 0, sizeof(hints));
hints.ai_flags = AI_PASSIVE; /* ignored if host is not ""(->NULL) */
hints.ai_flags |= AI_NUMERICHOST | AI_NUMERICSERV;
hints.ai_socktype = socktype;
- hints.ai_family = PF_INET;
+ hints.ai_family = inet_ai_family_from_address(addr, &err);
+ if (err) {
+ error_propagate(errp, err);
+ goto fail_return;
+ }
+ if (family != PF_UNSPEC) {
+ /* Guest must use same family as host (for now). */
+ if (hints.ai_family != PF_UNSPEC && hints.ai_family != family) {
+ error_setg(errp,
+ "unexpected address family for %s: expecting %s",
+ str, family == PF_INET ? "ipv4" : "ipv6");
+ goto fail_return;
+ }
+ hints.ai_family = family;
+ }
+
+ /* For backward compatibility, treat an empty host spec as IPv4. */
+ if (*addr->host == '\0' && hints.ai_family == PF_UNSPEC) {
+ hints.ai_family = PF_INET;
+ }
/*
* Calling getaddrinfo for guest addresses is dubious, but addresses are
@@ -768,8 +798,8 @@ void hmp_hostfwd_remove(Monitor *mon, const QDict *qdict)
flags |= SLIRP_HOSTFWD_UDP;
}
- if (parse_hostfwd_sockaddr(p, is_udp ? SOCK_DGRAM : SOCK_STREAM,
- &host_addr, &error) < 0) {
+ if (parse_hostfwd_sockaddr(p, PF_UNSPEC, is_udp ? SOCK_DGRAM : SOCK_STREAM,
+ &host_addr, /*v6_only=*/NULL, &error) < 0) {
goto fail_syntax;
}
@@ -794,6 +824,7 @@ static int slirp_hostfwd(SlirpState *s, const char *redir_str, Error **errp)
Error *error = NULL;
int flags = 0;
int port;
+ bool v6_only;
g_assert(redir_str != NULL);
p = redir_str;
@@ -811,14 +842,19 @@ static int slirp_hostfwd(SlirpState *s, const char *redir_str, Error **errp)
goto fail_syntax;
}
- if (parse_hostfwd_sockaddr(buf, is_udp ? SOCK_DGRAM : SOCK_STREAM,
- &host_addr, &error) < 0) {
+ if (parse_hostfwd_sockaddr(buf, PF_UNSPEC,
+ is_udp ? SOCK_DGRAM : SOCK_STREAM,
+ &host_addr, &v6_only, &error) < 0) {
error_prepend(&error, "For host address: ");
goto fail_syntax;
}
+ if (v6_only) {
+ flags |= SLIRP_HOSTFWD_V6ONLY;
+ }
- if (parse_hostfwd_sockaddr(p, is_udp ? SOCK_DGRAM : SOCK_STREAM,
- &guest_addr, &error) < 0) {
+ if (parse_hostfwd_sockaddr(p, host_addr.ss_family,
+ is_udp ? SOCK_DGRAM : SOCK_STREAM,
+ &guest_addr, /*v6_only=*/NULL, &error) < 0) {
error_prepend(&error, "For guest address: ");
goto fail_syntax;
}
@@ -37,6 +37,17 @@ def test_qmp_hostfwd_ipv4(self):
self.assertEquals(self.hmc('hostfwd_add udp::65042-:42'), '')
self.assertEquals(self.hmc('hostfwd_remove udp::65042'),
'host forwarding rule for udp::65042 removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv4-:22'), '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp::65022,ipv4'),
+ 'host forwarding rule for tcp::65022,ipv4 removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv4=on-:22'),
+ '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp::65022,ipv4=on'),
+ 'host forwarding rule for tcp::65022,ipv4=on removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv6=off-:22'),
+ '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp::65022,ipv6=off'),
+ 'host forwarding rule for tcp::65022,ipv6=off removed\r\n')
def test_qmp_hostfwd_ipv4_functional_errors(self):
"""Verify handling of various kinds of errors given valid addresses."""
@@ -89,3 +100,86 @@ def test_qmp_hostfwd_ipv4_parsing_errors(self):
self.assertEquals(self.hmc('hostfwd_add ::66-:0'),
"Invalid host forwarding rule '::66-:0'" + \
" (For guest address: invalid port '0')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv4=abc-:22'),
+ "Invalid host forwarding rule" + \
+ " 'tcp::65022,ipv4=abc-:22' (For host address:" + \
+ " error parsing 'ipv4' flag '=abc')\r\n")
+
+ def test_qmp_hostfwd_ipv6(self):
+ self.vm.add_args('-nodefaults',
+ '-netdev', 'user,id=vnet',
+ '-device', 'virtio-net,netdev=vnet')
+ self.vm.launch()
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp:[::1]:65022-[fe80::1]:22'),
+ '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp:[::1]:65022'),
+ 'host forwarding rule for tcp:[::1]:65022 removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add udp:[::1]:65042-[fe80::1]:42'),
+ '')
+ self.assertEquals(self.hmc('hostfwd_remove udp:[::1]:65042'),
+ 'host forwarding rule for udp:[::1]:65042 removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv6=on-:22'), '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp::65022,ipv6=on'),
+ 'host forwarding rule for tcp::65022,ipv6=on removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv4=off-:22'), '')
+ self.assertEquals(self.hmc('hostfwd_remove vnet tcp::65022,ipv4=off'),
+ 'host forwarding rule for tcp::65022,ipv4=off removed\r\n')
+
+ def test_qmp_hostfwd_ipv6_functional_errors(self):
+ """Verify handling of various kinds of errors given valid addresses."""
+ self.vm.add_args('-nodefaults',
+ '-netdev', 'user,id=vnet',
+ '-device', 'virtio-net,netdev=vnet')
+ self.vm.launch()
+ self.assertEquals(self.hmc('hostfwd_remove :[::1]:65022'),
+ 'host forwarding rule for :[::1]:65022 not found\r\n')
+ self.assertEquals(self.hmc('hostfwd_add udp:[::1]:65042-[fe80::1]:42'),
+ '')
+ self.assertEquals(self.hmc('hostfwd_add udp:[::1]:65042-[fe80::1]:42'),
+ "Could not set up host forwarding rule" + \
+ " 'udp:[::1]:65042-[fe80::1]:42': Address already in use\r\n")
+ self.assertEquals(self.hmc('hostfwd_remove :[::1]:65042'),
+ 'host forwarding rule for :[::1]:65042 not found\r\n')
+ self.assertEquals(self.hmc('hostfwd_remove udp:[::1]:65042'),
+ 'host forwarding rule for udp:[::1]:65042 removed\r\n')
+ self.assertEquals(self.hmc('hostfwd_remove udp:[::1]:65042'),
+ 'host forwarding rule for udp:[::1]:65042 not found\r\n')
+
+ def test_qmp_hostfwd_ipv6_errors(self):
+ """Verify handling of various kinds of errors."""
+ self.vm.add_args('-nodefaults',
+ '-netdev', 'user,id=vnet',
+ '-device', 'virtio-net,netdev=vnet')
+ self.vm.launch()
+ self.assertEquals(self.hmc('hostfwd_add :[::1-'),
+ "Invalid host forwarding rule ':[::1-'" + \
+ " (For host address: error parsing IPv6 address '[::1')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[::1]:66-[fe80::1'),
+ "Invalid host forwarding rule ':[::1]:66-[fe80::1'" + \
+ " (For guest address: error parsing IPv6 address" + \
+ " '[fe80::1')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[:::]:66-foo'),
+ "Invalid host forwarding rule ':[:::]:66-foo'" + \
+ " (For host address: address resolution failed for" + \
+ " '[:::]:66': Name or service not known)\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[::1]-foo'),
+ "Invalid host forwarding rule ':[::1]-foo'" + \
+ " (For host address: error parsing IPv6 address '[::1]')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[::1]:66-[foo]'),
+ "Invalid host forwarding rule ':[::1]:66-[foo]'" + \
+ " (For guest address: error parsing IPv6 address '[foo]')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[::1]:66-[fe80::1]:0'),
+ "Invalid host forwarding rule ':[::1]:66-[fe80::1]:0'" + \
+ " (For guest address: invalid port '0')\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :[::1]:66-1.2.3.4:66'),
+ "Invalid host forwarding rule ':[::1]:66-1.2.3.4:66'" + \
+ " (For guest address: address resolution failed for" + \
+ " '1.2.3.4:66': Address family for hostname not supported)\r\n")
+ self.assertEquals(self.hmc('hostfwd_add :1.2.3.4:66-[fe80::1]:66'),
+ "Invalid host forwarding rule ':1.2.3.4:66-[fe80::1]:66'" + \
+ " (For guest address: address resolution failed for" + \
+ " '[fe80::1]:66': Address family for hostname not" + \
+ " supported)\r\n")
+ self.assertEquals(self.hmc('hostfwd_add vnet tcp::65022,ipv6=abc-:22'),
+ "Invalid host forwarding rule 'tcp::65022,ipv6=abc-:22'" + \
+ " (For host address: error parsing 'ipv6' flag '=abc')\r\n")
Net option "-hostfwd" now supports IPv6 addresses. Commands hostfwd_add, hostfwd_remove now support IPv6 addresses. Tested: avocado run tests/acceptance/hostfwd.py Signed-off-by: Doug Evans <dje@google.com> --- Changes from v5: Recognize ipv4=,ipv6= options. hmp-commands.hx | 18 ++++++- net/slirp.c | 54 +++++++++++++++++---- tests/acceptance/hostfwd.py | 94 +++++++++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+), 11 deletions(-)