This will make a best-effort attempt to construct an NBD URI for
connecting back to the current server. In many cases this is not
really possible (eg. if we were connected with nbd_connect_socket),
and it's not guaranteed to be correct.
---
generator/API.ml | 28 +++++++-
lib/uri.c | 167 +++++++++++++++++++++++++++++++++++++++++++
tests/aio-connect.c | 19 +++++
tests/connect-tcp.c | 20 ++++++
tests/connect-unix.c | 20 ++++++
tests/connect-uri.c | 17 +++++
6 files changed, 269 insertions(+), 2 deletions(-)
diff --git a/generator/API.ml b/generator/API.ml
index 6300b1c..2f1baa8 100644
--- a/generator/API.ml
+++ b/generator/API.ml
@@ -1383,7 +1383,7 @@ compiled with gnutls; you can test whether this is the case
with L<nbd_supports_tls(3)>.";
see_also = [URLLink
"https://github.com/NetworkBlockDevice/nbd/blob/master/doc/uri.md";
Link "set_export_name"; Link "set_tls";
- Link "set_opt_mode"];
+ Link "set_opt_mode"; Link "get_uri"];
};
"connect_unix", {
@@ -2875,7 +2875,30 @@ to support TLS encryption, or false if not.";
longdesc = "\
Returns true if libnbd was compiled with libxml2 which is required
to support NBD URIs, or false if not.";
- see_also = [Link "connect_uri"; Link "aio_connect_uri"];
+ see_also = [Link "connect_uri"; Link "aio_connect_uri";
+ Link "get_uri"];
+ };
+
+ "get_uri", {
+ default_call with
+ args = []; ret = RString;
+ permitted_states = [ Connecting; Negotiating; Connected; Closed; Dead ];
+ shortdesc = "construct an NBD URI for a connection";
+ longdesc = "\
+This makes a best effort attempt to construct an NBD URI which
+could be used to connect to this NBD server (eg. using
+L<nbd_connect_uri(3)>).
+
+The URI returned is not guaranteed to work, and in some cases
+(eg. if connected with L<nbd_connect_socket(3)>) it is not possible
+at all. Even if a URI is returned it may not be optimal.
+
+On error, L<nbd_get_errno(3)> will be set to C<ENOTSUP> if the
+library was compiled without support for URIs. In other error
+cases, L<nbd_get_errno(3)> and L<nbd_get_error(3)> should contain
+information about why constructing a URI was not possible.";
+ see_also = [Link "connect_uri"; Link "aio_connect_uri";
+ Link "supports_uri"];
};
]
@@ -3009,6 +3032,7 @@ let first_version = [
(* Added in 1.7.x development cycle, will be stable and supported in 1.8. *)
"set_private_data", (1, 8);
"get_private_data", (1, 8);
+ "get_uri", (1, 8);
(* These calls are proposed for a future version of libnbd, but
* have not been added to any released version so far.
diff --git a/lib/uri.c b/lib/uri.c
index 9f5a290..06eda19 100644
--- a/lib/uri.c
+++ b/lib/uri.c
@@ -25,6 +25,14 @@
#include <string.h>
#include <errno.h>
#include <assert.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <netdb.h>
+
+#ifdef HAVE_LINUX_VM_SOCKETS_H
+#include <linux/vm_sockets.h>
+#endif
#include "internal.h"
#include "vector.h"
@@ -335,6 +343,158 @@ cleanup:
return ret;
}
+/* This is best effort. If we didn't save enough information when
+ * connecting then return NULL but try to set errno and the error
+ * string to something useful.
+ */
+
+static int append_query_params (char **query_params,
+ const char *key, const char *value);
+
+char *
+nbd_unlocked_get_uri (struct nbd_handle *h)
+{
+ xmlURI uri = { 0 };
+ bool using_tls;
+ char *server = NULL;
+ char *query_params = NULL;
+ char *path = NULL;
+ char *ret = NULL;
+
+ if (h->tls == 2) /* TLS == require */
+ using_tls = true;
+ else if (h->tls_negotiated)
+ using_tls = true;
+ else
+ using_tls = false;
+
+ /* Set scheme, server or socket. */
+ if (h->hostname && h->port) {
+ uri.scheme = using_tls ? "nbds" : "nbd";
+ if (asprintf (&server, "%s:%s", h->hostname, h->port) == -1) {
+ set_error (errno, "asprintf");
+ goto out;
+ }
+ uri.server = server;
+ }
+
+ else if (h->connaddrlen > 0) {
+ switch (h->connaddr.ss_family) {
+ case AF_INET:
+ case AF_INET6: {
+ int err;
+ char host[NI_MAXHOST];
+ char serv[NI_MAXSERV];
+
+ uri.scheme = using_tls ? "nbds" : "nbd";
+ err = getnameinfo ((struct sockaddr *) &h->connaddr, h->connaddrlen,
+ host, sizeof host, serv, sizeof serv, NI_NUMERICHOST);
+ if (err != 0) {
+ set_error (0, "getnameinfo: %s", gai_strerror (err));
+ goto out;
+ }
+ if (asprintf (&server, "%s:%s", host, serv) == -1) {
+ set_error (errno, "asprintf");
+ goto out;
+ }
+ uri.server = server;
+ break;
+ }
+
+ case AF_UNIX: {
+ struct sockaddr_un *sun = (struct sockaddr_un *) &h->connaddr;
+
+ if (sun->sun_path[0] == '\0') {
+ /* Unix domain sockets in the abstract namespace are in theory
+ * supported in NBD URIs, but libxml2 cannot handle them so
+ * libnbd cannot use them here or in nbd_connect_uri.
+ */
+ set_error (EPROTONOSUPPORT, "Unix domain sockets in the "
+ "abstract namespace are not yet supported");
+ goto out;
+ }
+
+ uri.scheme = using_tls ? "nbds+unix" : "nbd+unix";
+ if (append_query_params (&query_params, "socket", sun->sun_path)
== -1)
+ goto out;
+ /* You have to set this otherwise xmlSaveUri generates bogus
+ * URIs "nbd+unix:/?socket=..."
+ */
+ uri.server = "";
+ break;
+ }
+
+#ifdef AF_VSOCK
+ case AF_VSOCK: {
+ struct sockaddr_vm *svm = (struct sockaddr_vm *) &h->connaddr;
+
+ uri.scheme = using_tls ? "nbds+vsock" : "nbd+vsock";
+ if (asprintf (&server, "%u:%u", svm->svm_cid, svm->svm_port) ==
-1) {
+ set_error (errno, "asprintf");
+ goto out;
+ }
+ uri.server = server;
+ break;
+ }
+#endif
+
+ default:
+ set_error (EAFNOSUPPORT,
+ "address family %d not supported", h->connaddr.ss_family);
+ goto out;
+ }
+ }
+
+ else {
+ set_error (EINVAL, "cannot construct a URI for this connection type");
+ goto out;
+ }
+
+ /* Set other fields. */
+ if (h->tls_username)
+ uri.user = h->tls_username;
+ if (h->export_name) {
+ if (asprintf (&path, "/%s", h->export_name) == -1) {
+ set_error (errno, "asprintf");
+ goto out;
+ }
+ uri.path = path;
+ }
+ if (h->tls_psk_file) {
+ if (append_query_params (&query_params,
+ "tls-psk-file", h->tls_psk_file) == -1)
+ goto out;
+ }
+
+ uri.query_raw = query_params;
+
+ /* Construct the final URI and return it. */
+ ret = (char *) xmlSaveUri (&uri);
+ if (ret == NULL)
+ set_error (errno, "xmlSaveUri failed");
+ out:
+ free (server);
+ free (query_params);
+ free (path);
+ return ret;
+}
+
+static int
+append_query_params (char **query_params, const char *key, const char *value)
+{
+ char *old_query_params = *query_params;
+
+ if (asprintf (query_params, "%s%s%s=%s",
+ old_query_params ? : "",
+ old_query_params ? "&" : "",
+ key, value) == -1) {
+ set_error (errno, "asprintf");
+ return -1;
+ }
+ free (old_query_params);
+ return 0;
+}
+
#else /* !HAVE_LIBXML2 */
#define NOT_SUPPORTED_ERROR \
@@ -354,4 +514,11 @@ nbd_unlocked_aio_connect_uri (struct nbd_handle *h, const char
*raw_uri)
return -1;
}
+char *
+nbd_unlocked_get_uri (struct nbd_handle *h)
+{
+ set_error (ENOTSUP, NOT_SUPPORTED_ERROR);
+ return NULL;
+}
+
#endif /* !HAVE_LIBXML2 */
diff --git a/tests/aio-connect.c b/tests/aio-connect.c
index 50e9055..0584005 100644
--- a/tests/aio-connect.c
+++ b/tests/aio-connect.c
@@ -43,6 +43,7 @@ main (int argc, char *argv[])
pid_t pid;
size_t i;
struct sockaddr_in addr;
+ char *actual_uri, *expected_uri;
unlink (PIDFILE);
@@ -96,6 +97,24 @@ main (int argc, char *argv[])
}
}
+ /* libnbd should be able to construct a URI for this connection. */
+ if (asprintf (&expected_uri, "nbd://127.0.0.1:%s/", port_str) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ actual_uri = nbd_get_uri (nbd);
+ if (actual_uri == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (strcmp (actual_uri, expected_uri) != 0) {
+ fprintf (stderr, "%s: actual URI %s != expected URI %s\n",
+ argv[0], actual_uri, expected_uri);
+ exit (EXIT_FAILURE);
+ }
+ free (actual_uri);
+ free (expected_uri);
+
if (nbd_shutdown (nbd, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
diff --git a/tests/connect-tcp.c b/tests/connect-tcp.c
index d7a36e4..e96b1ec 100644
--- a/tests/connect-tcp.c
+++ b/tests/connect-tcp.c
@@ -22,6 +22,7 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <time.h>
@@ -38,6 +39,7 @@ main (int argc, char *argv[])
char port_str[16];
pid_t pid;
size_t i;
+ char *actual_uri, *expected_uri;
unlink (PIDFILE);
@@ -79,6 +81,24 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
+ /* libnbd should be able to construct a URI for this connection. */
+ if (asprintf (&expected_uri, "nbd://localhost:%s/", port_str) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ actual_uri = nbd_get_uri (nbd);
+ if (actual_uri == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (strcmp (actual_uri, expected_uri) != 0) {
+ fprintf (stderr, "%s: actual URI %s != expected URI %s\n",
+ argv[0], actual_uri, expected_uri);
+ exit (EXIT_FAILURE);
+ }
+ free (actual_uri);
+ free (expected_uri);
+
if (nbd_shutdown (nbd, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
diff --git a/tests/connect-unix.c b/tests/connect-unix.c
index 8c18166..0000b98 100644
--- a/tests/connect-unix.c
+++ b/tests/connect-unix.c
@@ -22,6 +22,7 @@
#include <stdio.h>
#include <stdlib.h>
+#include <string.h>
#include <fcntl.h>
#include <unistd.h>
@@ -36,6 +37,7 @@ main (int argc, char *argv[])
struct nbd_handle *nbd;
pid_t pid;
size_t i;
+ char *actual_uri, *expected_uri;
if (mkstemp (socket) == -1) {
perror (socket);
@@ -77,6 +79,24 @@ main (int argc, char *argv[])
exit (EXIT_FAILURE);
}
+ /* libnbd should be able to construct a URI for this connection. */
+ if (asprintf (&expected_uri, "nbd+unix:///?socket=%s", socket) == -1) {
+ perror ("asprintf");
+ exit (EXIT_FAILURE);
+ }
+ actual_uri = nbd_get_uri (nbd);
+ if (actual_uri == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (strcmp (actual_uri, expected_uri) != 0) {
+ fprintf (stderr, "%s: actual URI %s != expected URI %s\n",
+ argv[0], actual_uri, expected_uri);
+ exit (EXIT_FAILURE);
+ }
+ free (actual_uri);
+ free (expected_uri);
+
if (nbd_shutdown (nbd, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
diff --git a/tests/connect-uri.c b/tests/connect-uri.c
index 6e7d168..9093247 100644
--- a/tests/connect-uri.c
+++ b/tests/connect-uri.c
@@ -35,6 +35,7 @@ main (int argc, char *argv[])
struct nbd_handle *nbd;
pid_t pid;
size_t i;
+ char *get_uri;
#ifdef SOCKET
unlink (SOCKET);
@@ -89,6 +90,22 @@ main (int argc, char *argv[])
}
}
+ /* Usually the URI returned by nbd_get_uri should be the same as the
+ * one passed to nbd_connect_uri, or at least it will be in our test
+ * cases.
+ */
+ get_uri = nbd_get_uri (nbd);
+ if (get_uri == NULL) {
+ fprintf (stderr, "%s\n", nbd_get_error ());
+ exit (EXIT_FAILURE);
+ }
+ if (strcmp (URI, get_uri) != 0) {
+ fprintf (stderr, "%s: connect URI %s != get URI %s\n",
+ argv[0], URI, get_uri);
+ exit (EXIT_FAILURE);
+ }
+ free (get_uri);
+
if (nbd_shutdown (nbd, 0) == -1) {
fprintf (stderr, "%s\n", nbd_get_error ());
exit (EXIT_FAILURE);
--
2.29.0.rc2