From: Doug Evans <dje@google.com>
To: qemu-devel@nongnu.org
Cc: "Samuel Thibault" <samuel.thibault@ens-lyon.org>,
"Daniel P . Berrangé" <berrange@redhat.com>,
"Doug Evans" <dje@google.com>
Subject: [PATCH v6 4/4] net: Extend host forwarding to support IPv6
Date: Wed, 14 Apr 2021 20:39:25 -0700 [thread overview]
Message-ID: <20210415033925.1290401-5-dje@google.com> (raw)
In-Reply-To: <20210415033925.1290401-1-dje@google.com>
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(-)
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 435c591a1c..05f88e893b 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -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
{
diff --git a/net/slirp.c b/net/slirp.c
index 4be065c30b..82d4b71bef 100644
--- a/net/slirp.c
+++ b/net/slirp.c
@@ -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;
}
diff --git a/tests/acceptance/hostfwd.py b/tests/acceptance/hostfwd.py
index 9b9db142c3..f8493c5bdc 100644
--- a/tests/acceptance/hostfwd.py
+++ b/tests/acceptance/hostfwd.py
@@ -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")
--
2.31.1.295.g9ea45b61b8-goog
next prev parent reply other threads:[~2021-04-15 3:46 UTC|newest]
Thread overview: 22+ messages / expand[flat|nested] mbox.gz Atom feed top
2021-04-15 3:39 [PATCH v6 0/4] Add support for ipv6 host forwarding Doug Evans
2021-04-15 3:39 ` [PATCH v6 1/4] slirp: Advance libslirp submodule to add ipv6 host-forward support Doug Evans
2021-05-07 15:23 ` Marc-André Lureau
2021-05-07 15:46 ` Doug Evans
2021-05-12 16:42 ` Doug Evans
2021-05-12 17:18 ` Marc-André Lureau
2021-05-12 19:50 ` Doug Evans
2021-05-12 20:14 ` Marc-André Lureau
2021-04-15 3:39 ` [PATCH v6 2/4] util/qemu-sockets.c: Split host:port parsing out of inet_parse Doug Evans
2021-05-07 15:23 ` Marc-André Lureau
2021-05-25 19:37 ` RFC: IPv6 hostfwd command line syntax [was Re: [PATCH v6 2/4] util/qemu-sockets.c: Split host:port parsing out of inet_parse] Doug Evans
2021-05-26 13:57 ` Daniel P. Berrangé
2021-05-26 15:26 ` Doug Evans
2021-05-26 15:29 ` Daniel P. Berrangé
2021-04-15 3:39 ` [PATCH v6 3/4] net/slirp.c: Refactor address parsing Doug Evans
2021-04-15 15:36 ` Doug Evans
2021-05-07 15:29 ` Marc-André Lureau
2021-04-15 3:39 ` Doug Evans [this message]
2021-04-29 3:37 ` [PATCH v6 0/4] Add support for ipv6 host forwarding Doug Evans
2021-05-05 15:21 ` Doug Evans
2021-05-05 16:13 ` Philippe Mathieu-Daudé
2021-05-05 16:15 ` Philippe Mathieu-Daudé
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20210415033925.1290401-5-dje@google.com \
--to=dje@google.com \
--cc=berrange@redhat.com \
--cc=qemu-devel@nongnu.org \
--cc=samuel.thibault@ens-lyon.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.