[libvirt] [PATCH 0/3] libxl: misc fixes for migration

While working on the {Begin,End}API improvements I noticed a few problems in the migration code. The first two are trival, but the third describes a problem I encountered while testing, and provides a potential fix. See the patch for more details. Jim Fehlig (3): libxl: dont dereference NULL libxlDomainObjPrivatePtr libxl: remove needless 'else' in libxlDomainMigrationPrepare util: introduce virSocketAddrParseQuiet src/libvirt_private.syms | 1 + src/libxl/libxl_migration.c | 10 ++++---- src/rpc/virnetsocket.c | 2 +- src/util/virsocketaddr.c | 60 +++++++++++++++++++++++++++++++++------------ src/util/virsocketaddr.h | 4 +++ 5 files changed, 55 insertions(+), 22 deletions(-) -- 2.16.2

In libxlDomainMigrationPrepare it is possible to dereference a NULL libxlDomainObjPrivatePtr in early error paths. Check for a valid 'priv' before using it. Signed-off-by: Jim Fehlig <jfehlig@suse.com> --- src/libxl/libxl_migration.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/libxl/libxl_migration.c b/src/libxl/libxl_migration.c index 7dc39ae02..c1c23dab3 100644 --- a/src/libxl/libxl_migration.c +++ b/src/libxl/libxl_migration.c @@ -804,9 +804,10 @@ libxlDomainMigrationPrepare(virConnectPtr dconn, } VIR_FREE(socks); virObjectUnref(args); - virPortAllocatorRelease(priv->migrationPort); - priv->migrationPort = 0; - + if (priv) { + virPortAllocatorRelease(priv->migrationPort); + priv->migrationPort = 0; + } /* Remove virDomainObj from domain list */ if (vm) { virDomainObjListRemove(driver->domains, vm); -- 2.16.2

On 03/19/2018 07:28 PM, Jim Fehlig wrote:
In libxlDomainMigrationPrepare it is possible to dereference a NULL libxlDomainObjPrivatePtr in early error paths. Check for a valid 'priv' before using it.
Signed-off-by: Jim Fehlig <jfehlig@suse.com> --- src/libxl/libxl_migration.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-)
Reviewed-by: John Ferlan <jferlan@redhat.com> John

Signed-off-by: Jim Fehlig <jfehlig@suse.com> --- src/libxl/libxl_migration.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/libxl/libxl_migration.c b/src/libxl/libxl_migration.c index c1c23dab3..f0001f9f7 100644 --- a/src/libxl/libxl_migration.c +++ b/src/libxl/libxl_migration.c @@ -733,9 +733,8 @@ libxlDomainMigrationPrepare(virConnectPtr dconn, _("missing host in migration URI: %s"), uri_in); goto error; - } else { - hostname = uri->server; } + hostname = uri->server; if (uri->port == 0) { if (virPortAllocatorAcquire(driver->migrationPorts, &port) < 0) -- 2.16.2

On 03/19/2018 07:28 PM, Jim Fehlig wrote:
Signed-off-by: Jim Fehlig <jfehlig@suse.com> --- src/libxl/libxl_migration.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
Reviewed-by: John Ferlan <jferlan@redhat.com> John

When preparing for migration, the libxl driver creates a new TCP listen socket for the incoming migration by calling virNetSocketNewListenTCP, passing the destination host name. virNetSocketNewListenTCP calls virSocketAddrParse to check if the host name is a wildcard address, in which case it avoids adding the AI_ADDRCONFIG flag to the hints passed to getaddrinfo. If the host name is not an IP address, virSocketAddrParse reports an error error : virSocketAddrParseInternal:121 : Cannot parse socket address 'myhost.example.com': Name or service not known But virNetSocketNewListenTCP succeeds regardless and the overall migration operation succeeds. Introduce virSocketAddrParseQuiet and use it when simply testing if a host name/addr is parsable. Signed-off-by: Jim Fehlig <jfehlig@suse.com> --- A brute-force hack that also works is to remove AI_NUMERICHOST flag from hints in virSocketAddrParseInternal, but I'm not sure what sort of havoc that would wreak Perhaps I should ask the obvious question first: Is virSocketAddrParse expected to work with a host name? I suspec there are other callers that could pass a name as well as a numeric IP addr, depending on URI provided by user. src/libvirt_private.syms | 1 + src/rpc/virnetsocket.c | 2 +- src/util/virsocketaddr.c | 60 +++++++++++++++++++++++++++++++++++------------- src/util/virsocketaddr.h | 4 ++++ 4 files changed, 50 insertions(+), 17 deletions(-) diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c3e4fd23d..acacbaeb1 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2726,6 +2726,7 @@ virSocketAddrNumericFamily; virSocketAddrParse; virSocketAddrParseIPv4; virSocketAddrParseIPv6; +virSocketAddrParseQuiet; virSocketAddrPrefixToNetmask; virSocketAddrPTRDomain; virSocketAddrSetIPv4Addr; diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index 2d41a716b..07166ecc9 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -333,7 +333,7 @@ int virNetSocketNewListenTCP(const char *nodename, * startup in most cases. */ if (nodename && - !(virSocketAddrParse(&tmp_addr, nodename, AF_UNSPEC) > 0 && + !(virSocketAddrParseQuiet(&tmp_addr, nodename, AF_UNSPEC) > 0 && virSocketAddrIsWildcard(&tmp_addr))) hints.ai_flags |= AI_ADDRCONFIG; diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index 95b527436..711c634e8 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -126,29 +126,24 @@ virSocketAddrParseInternal(struct addrinfo **res, return 0; } -/** - * virSocketAddrParse: - * @val: a numeric network address IPv4 or IPv6 - * @addr: where to store the return value, optional. - * @family: address family to pass down to getaddrinfo - * - * Mostly a wrapper for getaddrinfo() extracting the address storage - * from the numeric string like 1.2.3.4 or 2001:db8:85a3:0:0:8a2e:370:7334 - * - * Returns the length of the network address or -1 in case of error. - */ -int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) +static int +virSockAddrParseVerbose(virSocketAddrPtr addr, + const char *val, + int family, + bool verbose) { int len; struct addrinfo *res; - if (virSocketAddrParseInternal(&res, val, family, true) < 0) + if (virSocketAddrParseInternal(&res, val, family, verbose) < 0) return -1; if (res == NULL) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("No socket addresses found for '%s'"), - val); + if (verbose) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("No socket addresses found for '%s'"), + val); + } return -1; } @@ -162,6 +157,39 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) return len; } + +/** + * virSocketAddrParse: + * @val: a numeric network address IPv4 or IPv6 + * @addr: where to store the return value, optional. + * @family: address family to pass down to getaddrinfo + * + * Mostly a wrapper for getaddrinfo() extracting the address storage + * from the numeric string like 1.2.3.4 or 2001:db8:85a3:0:0:8a2e:370:7334 + * + * Returns the length of the network address or -1 in case of error. + */ +int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) +{ + return virSockAddrParseVerbose(addr, val, family, true); +} + +/** + * virSocketAddrParseQuiet: + * @val: a numeric network address IPv4 or IPv6 + * @addr: where to store the return value, optional. + * @family: address family to pass down to getaddrinfo + * + * A quiet version of virSocketAddrParse. No errors are reported in + * error paths. + * + * Returns the length of the network address or -1 in case of error. + */ +int virSocketAddrParseQuiet(virSocketAddrPtr addr, const char *val, int family) +{ + return virSockAddrParseVerbose(addr, val, family, false); +} + /* * virSocketAddrParseIPv4: * @val: an IPv4 numeric address diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index 80e792618..99bb7a949 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -92,6 +92,10 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family); +int virSocketAddrParseQuiet(virSocketAddrPtr addr, + const char *val, + int family); + int virSocketAddrParseIPv4(virSocketAddrPtr addr, const char *val); -- 2.16.2

On 03/19/2018 07:28 PM, Jim Fehlig wrote:
When preparing for migration, the libxl driver creates a new TCP listen socket for the incoming migration by calling virNetSocketNewListenTCP, passing the destination host name. virNetSocketNewListenTCP calls virSocketAddrParse to check if the host name is a wildcard address, in which case it avoids adding the AI_ADDRCONFIG flag to the hints passed to getaddrinfo. If the host name is not an IP address, virSocketAddrParse reports an error
error : virSocketAddrParseInternal:121 : Cannot parse socket address 'myhost.example.com': Name or service not known
But virNetSocketNewListenTCP succeeds regardless and the overall migration operation succeeds.
Introduce virSocketAddrParseQuiet and use it when simply testing if a host name/addr is parsable.
Signed-off-by: Jim Fehlig <jfehlig@suse.com> ---
A brute-force hack that also works is to remove AI_NUMERICHOST flag from hints in virSocketAddrParseInternal, but I'm not sure what sort of havoc that would wreak
Perhaps rather than quiet/verbose flag - maybe ParseInternal should add a new parameter "ai_flags"? Then add virSocketAddrParseName which would pass 0 instead of AI_NUMERICHOST and expect to parse the name. FWIW, I have a faint recollection of some issue with getaddrinfo and dns delays on lookups when looking up by name. Been a while since I've had to think about that though, so I could be wrong as the memory fades/ages. You could also search on some other direct callers of getaddrinfo in libvirt sources - there's some interesting uses.. I also note (ironically) that if @addr == NULL, then error is splatted in ParseInternal regardless of @reportError value.
Perhaps I should ask the obvious question first: Is virSocketAddrParse expected to work with a host name? I suspec there are other callers that could pass a name as well as a numeric IP addr, depending on URI provided by user.
Seems to me some comments [1] answer your question. There's a virSocketAddrNumericFamily - maybe that'll help you in deciding how to formulate things... Seems to be used by a few places already to determine whether one has a name or number (including the qemu migration path). FWIW: I also note callers to virSocketAddrNumericFamily don't necessarily handle the possible -1 return possibly... Looks like you're jumping into one of rabbit holes Laine usually falls into ;-)
src/libvirt_private.syms | 1 + src/rpc/virnetsocket.c | 2 +- src/util/virsocketaddr.c | 60 +++++++++++++++++++++++++++++++++++------------- src/util/virsocketaddr.h | 4 ++++ 4 files changed, 50 insertions(+), 17 deletions(-)
diff --git a/src/libvirt_private.syms b/src/libvirt_private.syms index c3e4fd23d..acacbaeb1 100644 --- a/src/libvirt_private.syms +++ b/src/libvirt_private.syms @@ -2726,6 +2726,7 @@ virSocketAddrNumericFamily; virSocketAddrParse; virSocketAddrParseIPv4; virSocketAddrParseIPv6; +virSocketAddrParseQuiet; virSocketAddrPrefixToNetmask; virSocketAddrPTRDomain; virSocketAddrSetIPv4Addr; diff --git a/src/rpc/virnetsocket.c b/src/rpc/virnetsocket.c index 2d41a716b..07166ecc9 100644 --- a/src/rpc/virnetsocket.c +++ b/src/rpc/virnetsocket.c @@ -333,7 +333,7 @@ int virNetSocketNewListenTCP(const char *nodename, * startup in most cases. */ if (nodename && - !(virSocketAddrParse(&tmp_addr, nodename, AF_UNSPEC) > 0 && + !(virSocketAddrParseQuiet(&tmp_addr, nodename, AF_UNSPEC) > 0 && virSocketAddrIsWildcard(&tmp_addr))) hints.ai_flags |= AI_ADDRCONFIG;
diff --git a/src/util/virsocketaddr.c b/src/util/virsocketaddr.c index 95b527436..711c634e8 100644 --- a/src/util/virsocketaddr.c +++ b/src/util/virsocketaddr.c @@ -126,29 +126,24 @@ virSocketAddrParseInternal(struct addrinfo **res, return 0; }
-/** - * virSocketAddrParse: - * @val: a numeric network address IPv4 or IPv6 - * @addr: where to store the return value, optional. - * @family: address family to pass down to getaddrinfo - * - * Mostly a wrapper for getaddrinfo() extracting the address storage - * from the numeric string like 1.2.3.4 or 2001:db8:85a3:0:0:8a2e:370:7334 - *
[1]
- * Returns the length of the network address or -1 in case of error. - */ -int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) +static int +virSockAddrParseVerbose(virSocketAddrPtr addr, + const char *val, + int family, + bool verbose)
Could have gone with reportError like ParseInternal. John
{ int len; struct addrinfo *res;
- if (virSocketAddrParseInternal(&res, val, family, true) < 0) + if (virSocketAddrParseInternal(&res, val, family, verbose) < 0) return -1;
if (res == NULL) { - virReportError(VIR_ERR_SYSTEM_ERROR, - _("No socket addresses found for '%s'"), - val); + if (verbose) { + virReportError(VIR_ERR_SYSTEM_ERROR, + _("No socket addresses found for '%s'"), + val); + } return -1; }
@@ -162,6 +157,39 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) return len; }
+ +/** + * virSocketAddrParse: + * @val: a numeric network address IPv4 or IPv6 + * @addr: where to store the return value, optional. + * @family: address family to pass down to getaddrinfo + * + * Mostly a wrapper for getaddrinfo() extracting the address storage + * from the numeric string like 1.2.3.4 or 2001:db8:85a3:0:0:8a2e:370:7334 + * + * Returns the length of the network address or -1 in case of error. + */ +int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family) +{ + return virSockAddrParseVerbose(addr, val, family, true); +} + +/** + * virSocketAddrParseQuiet: + * @val: a numeric network address IPv4 or IPv6 + * @addr: where to store the return value, optional. + * @family: address family to pass down to getaddrinfo + * + * A quiet version of virSocketAddrParse. No errors are reported in + * error paths. + * + * Returns the length of the network address or -1 in case of error. + */ +int virSocketAddrParseQuiet(virSocketAddrPtr addr, const char *val, int family) +{ + return virSockAddrParseVerbose(addr, val, family, false); +} + /* * virSocketAddrParseIPv4: * @val: an IPv4 numeric address diff --git a/src/util/virsocketaddr.h b/src/util/virsocketaddr.h index 80e792618..99bb7a949 100644 --- a/src/util/virsocketaddr.h +++ b/src/util/virsocketaddr.h @@ -92,6 +92,10 @@ int virSocketAddrParse(virSocketAddrPtr addr, const char *val, int family);
+int virSocketAddrParseQuiet(virSocketAddrPtr addr, + const char *val, + int family); + int virSocketAddrParseIPv4(virSocketAddrPtr addr, const char *val);

On Mon, Mar 19, Jim Fehlig wrote:
While working on the {Begin,End}API improvements I noticed a few problems in the migration code. The first two are trival, but the third describes a problem I encountered while testing, and provides a potential fix. See the patch for more details.
I tried these changes with master. Without them the receiving side reports this: Mär 20 08:53:06 macintyre-old libvirtd[1275]: 2018-03-20 07:53:06.161+0000: 1317: error : virSocketAddrParseInternal:121 : Cannot parse socket address 'macintyre-old.arch.suse.de': Name or service not known Mär 20 08:53:06 macintyre-old libvirtd[1275]: 2018-03-20 07:53:06.162+0000: 1317: error : virNetSocketNewListenTCP:389 : Unable to bind to port: Cannot assign requested address Mär 20 08:53:06 macintyre-old libvirtd[1275]: 2018-03-20 07:53:06.162+0000: 1317: error : libxlDomainMigrationPrepare:759 : operation failed: Fail to create socket for incoming migration With them just the parse error disappeared: Mär 20 09:35:52 macintyre-old libvirtd[4527]: 2018-03-20 08:35:52.521+0000: 4531: error : virNetSocketNewListenTCP:389 : Unable to bind to port: Cannot assign requested address Mär 20 09:35:52 macintyre-old libvirtd[4527]: 2018-03-20 08:35:52.521+0000: 4531: error : libxlDomainMigrationPrepare:758 : operation failed: Fail to create socket for incoming migration Not sure why it fails just for me. Olaf

On Tue, Mar 20, Olaf Hering wrote:
Mär 20 09:35:52 macintyre-old libvirtd[4527]: 2018-03-20 08:35:52.521+0000: 4531: error : virNetSocketNewListenTCP:389 : Unable to bind to port: Cannot assign requested address
After further debugging: 27672 16:04:46.774906 socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP) = 35 27672 16:04:46.775041 setsockopt(35, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0 27672 16:04:46.775172 setsockopt(35, SOL_IPV6, IPV6_V6ONLY, [1], 4) = 0 27672 16:04:46.775302 bind(35, {sa_family=AF_INET6, sin6_port=htons(49152), inet_pton(AF_INET6, "2620:113:80c0:8000:10:161:8:197", &sin6_addr), sin6_flowinfo=0, sin6_scope_id=0}, 28) = -1 EADDRNOTAVAIL (Cannot assign requested address) 27672 16:04:46.775455 gettid() = 27672 27672 16:04:46.775590 write(4, "2018-03-20 15:04:46.775+0000: 27672: error : virNetSocketNewListenTCP:389 : Unable to bind to port: Cannot assign requested address\n", 132) = 132 27672 16:04:46.775742 gettid() = 27672 27672 16:04:46.775875 write(4, "2018-03-20 15:04:46.775+0000: 27672: info : virObjectUnref:350 : OBJECT_UNREF: obj=0x7fa4bc003530\n", 98) = 98 27672 16:04:46.776026 gettid() = 27672 So for some reason libvirtd tries to bind to ipv6 even if the host does not have that ipv6 address at this point, only the link-local address. Not sure if there is a way to detect that within libvirt. Perhaps it should just move on with the runp list and try the next one? Olaf
participants (3)
-
Jim Fehlig
-
John Ferlan
-
Olaf Hering