summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Haller <thaller@redhat.com>2018-12-29 13:01:28 +0100
committerThomas Haller <thaller@redhat.com>2019-02-14 08:00:29 +0100
commit713e879d769f4aa95d1b15f5a584cc9cf4e9bd13 (patch)
tree7c7a94d5311b78bf6d8b3dd5b642dd67072c110d
parentd93845e2c2c67349abcfc1f5377d820f02247b8b (diff)
downloadNetworkManager-713e879d769f4aa95d1b15f5a584cc9cf4e9bd13.tar.gz
libnm: add NMSockAddrEndpoint API
NMSockAddrEndpoint is an immutable structure that contains the endpoint string of a service. It also includes the (naive) parsing of the host and port/service parts. This will be used for the endpoint of WireGuard's peers. But since endpoints are not something specific to WireGuard, give it a general name (and purpose) independent from WireGuard. Essentially, this structure takes a string in a manner that libnm understands, and uses it for node and service arguments for getaddrinfo(). NMSockAddrEndpoint allows to have endpoints that are not parsable into a host and port part. That is useful because our settings need to be able to hold invalid values. That is for forward compatibility (server sends a new endpoint format) and for better error handling (have invalid settings that can be constructed without loss, but fail later during the NMSetting:verify() step).
-rw-r--r--libnm-core/nm-core-internal.h19
-rw-r--r--libnm-core/nm-utils.c271
-rw-r--r--libnm-core/nm-utils.h2
-rw-r--r--libnm-core/tests/test-general.c154
4 files changed, 446 insertions, 0 deletions
diff --git a/libnm-core/nm-core-internal.h b/libnm-core/nm-core-internal.h
index 891ec0a7aa..19a914956e 100644
--- a/libnm-core/nm-core-internal.h
+++ b/libnm-core/nm-core-internal.h
@@ -614,6 +614,25 @@ gboolean _nm_setting_sriov_sort_vfs (NMSettingSriov *setting);
/*****************************************************************************/
+typedef struct _NMSockAddrEndpoint NMSockAddrEndpoint;
+
+NMSockAddrEndpoint *nm_sock_addr_endpoint_new (const char *endpoint);
+
+NMSockAddrEndpoint *nm_sock_addr_endpoint_ref (NMSockAddrEndpoint *self);
+void nm_sock_addr_endpoint_unref (NMSockAddrEndpoint *self);
+
+const char *nm_sock_addr_endpoint_get_endpoint (NMSockAddrEndpoint *self);
+const char *nm_sock_addr_endpoint_get_host (NMSockAddrEndpoint *self);
+gint32 nm_sock_addr_endpoint_get_port (NMSockAddrEndpoint *self);
+
+gboolean nm_sock_addr_endpoint_get_fixed_sockaddr (NMSockAddrEndpoint *self,
+ gpointer sockaddr);
+
+#define nm_auto_unref_sockaddrendpoint nm_auto(_nm_auto_unref_sockaddrendpoint)
+NM_AUTO_DEFINE_FCN_VOID0 (NMSockAddrEndpoint *, _nm_auto_unref_sockaddrendpoint, nm_sock_addr_endpoint_unref)
+
+/*****************************************************************************/
+
typedef struct _NMSettInfoSetting NMSettInfoSetting;
typedef struct _NMSettInfoProperty NMSettInfoProperty;
diff --git a/libnm-core/nm-utils.c b/libnm-core/nm-utils.c
index b5f9fb8676..c5855da196 100644
--- a/libnm-core/nm-utils.c
+++ b/libnm-core/nm-utils.c
@@ -59,6 +59,277 @@
* access points and devices, among other things.
*/
+/*****************************************************************************/
+
+struct _NMSockAddrEndpoint {
+ const char *host;
+ guint16 port;
+ guint refcount;
+ char endpoint[];
+};
+
+static gboolean
+NM_IS_SOCK_ADDR_ENDPOINT (const NMSockAddrEndpoint *self)
+{
+ return self && self->refcount > 0;
+}
+
+static const char *
+_parse_endpoint (char *str,
+ guint16 *out_port)
+{
+ char *s;
+ const char *s_port;
+ gint16 port;
+
+ /* Like
+ * - https://git.zx2c4.com/WireGuard/tree/src/tools/config.c?id=5e99a6d43fe2351adf36c786f5ea2086a8fe7ab8#n192
+ * - https://github.com/systemd/systemd/blob/911649fdd43f3a9158b847947724a772a5a45c34/src/network/netdev/wireguard.c#L614
+ */
+
+ g_strstrip (str);
+
+ if (!str[0])
+ return NULL;
+
+ if (str[0] == '[') {
+ str++;
+ s = strchr (str, ']');
+ if (!s)
+ return NULL;
+ if (s == str)
+ return NULL;
+ if (s[1] != ':')
+ return NULL;
+ if (!s[2])
+ return NULL;
+ *s = '\0';
+ s_port = &s[2];
+ } else {
+ s = strrchr (str, ':');
+ if (!s)
+ return NULL;
+ if (s == str)
+ return NULL;
+ if (!s[1])
+ return NULL;
+ *s = '\0';
+ s_port = &s[1];
+ }
+
+ if (!NM_STRCHAR_ALL (s_port, ch, (ch >= '0' && ch <= '9')))
+ return NULL;
+
+ port = _nm_utils_ascii_str_to_int64 (s_port, 10, 1, G_MAXUINT16, 0);
+ if (port == 0)
+ return NULL;
+
+ *out_port = port;
+ return str;
+}
+
+/**
+ * nm_sock_addr_endpoint_new:
+ * @endpoint: the endpoint string.
+ *
+ * This function cannot fail, even if the @endpoint is invalid.
+ * The reason is to allow NMSockAddrEndpoint also to be used
+ * for tracking invalid endpoints. Use nm_sock_addr_endpoint_get_host()
+ * to determine whether the endpoint is valid.
+ *
+ * Returns: (transfer full): the new #NMSockAddrEndpoint endpoint.
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_new (const char *endpoint)
+{
+ NMSockAddrEndpoint *ep;
+ gsize l_endpoint;
+ gsize l_host = 0;
+ gsize i;
+ gs_free char *host_clone = NULL;
+ const char *host;
+ guint16 port;
+
+ g_return_val_if_fail (endpoint, NULL);
+
+ l_endpoint = strlen (endpoint) + 1;
+
+ host = _parse_endpoint (nm_strndup_a (200, endpoint, l_endpoint - 1, &host_clone),
+ &port);
+
+ if (host)
+ l_host = strlen (host) + 1;
+
+ ep = g_malloc (sizeof (NMSockAddrEndpoint) + l_endpoint + l_host);
+ ep->refcount = 1;
+ memcpy (ep->endpoint, endpoint, l_endpoint);
+ if (host) {
+ i = l_endpoint;
+ memcpy (&ep->endpoint[i], host, l_host);
+ ep->host = &ep->endpoint[i];
+ ep->port = port;
+ } else {
+ ep->host = NULL;
+ ep->port = 0;
+ }
+ return ep;
+}
+
+/**
+ * nm_sock_addr_endpoint_ref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+NMSockAddrEndpoint *
+nm_sock_addr_endpoint_ref (NMSockAddrEndpoint *self)
+{
+ if (!self)
+ return NULL;
+
+ g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+ nm_assert (self->refcount < G_MAXUINT);
+
+ self->refcount++;
+ return self;
+}
+
+/**
+ * nm_sock_addr_endpoint_unref:
+ * @self: (allow-none): the #NMSockAddrEndpoint
+ */
+void
+nm_sock_addr_endpoint_unref (NMSockAddrEndpoint *self)
+{
+ if (!self)
+ return;
+
+ g_return_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self));
+
+ if (--self->refcount == 0)
+ g_free (self);
+}
+
+/**
+ * nm_sock_addr_endpoint_get_endpoint:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Gives the endpoint string. Since #NMSockAddrEndpoint's only
+ * information is the endpoint string, this can be used for comparing
+ * to instances for equality and order them lexically.
+ *
+ * Returns: (transfer none): the endpoint.
+ */
+const char *
+nm_sock_addr_endpoint_get_endpoint (NMSockAddrEndpoint *self)
+{
+ g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+ return self->endpoint;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_host:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: (transfer none): the parsed host part of the endpoint.
+ * If the endpoint is invalid, %NULL will be returned.
+ */
+const char *
+nm_sock_addr_endpoint_get_host (NMSockAddrEndpoint *self)
+{
+ g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), NULL);
+
+ return self->host;
+}
+
+/**
+ * nm_sock_addr_endpoint_get_port:
+ * @self: the #NMSockAddrEndpoint
+ *
+ * Returns: the parsed port part of the endpoint (the service).
+ * If the endpoint is invalid, -1 will be returned.
+ */
+gint32
+nm_sock_addr_endpoint_get_port (NMSockAddrEndpoint *self)
+{
+ g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), -1);
+
+ return self->host ? (int) self->port : -1;
+}
+
+gboolean
+nm_sock_addr_endpoint_get_fixed_sockaddr (NMSockAddrEndpoint *self,
+ gpointer sockaddr)
+{
+ int addr_family;
+ NMIPAddr addrbin;
+ const char *s;
+ guint scope_id = 0;
+
+ g_return_val_if_fail (NM_IS_SOCK_ADDR_ENDPOINT (self), FALSE);
+ g_return_val_if_fail (sockaddr, FALSE);
+
+ if (!self->host)
+ return FALSE;
+
+ if (nm_utils_parse_inaddr_bin (AF_UNSPEC, self->host, &addr_family, &addrbin))
+ goto good;
+
+ /* See if there is an IPv6 scope-id...
+ *
+ * Note that it does not make sense to persist connection profiles to disk,
+ * that refenrence a scope-id (because the interface's ifindex changes on
+ * reboot). However, we also support runtime only changes like `nmcli device modify`
+ * where nothing is persisted to disk. At least in that case, passing a scope-id
+ * might be reasonable. So, parse that too. */
+ s = strchr (self->host, '%');
+ if (!s)
+ return FALSE;
+
+ if ( s[1] == '\0'
+ || !NM_STRCHAR_ALL (&s[1], ch, (ch >= '0' && ch <= '9')))
+ return FALSE;
+
+ scope_id = _nm_utils_ascii_str_to_int64 (&s[1], 10, 0, G_MAXINT32, G_MAXUINT);
+ if (scope_id == G_MAXUINT && errno)
+ return FALSE;
+
+ {
+ gs_free char *tmp_str = NULL;
+ const char *host_part;
+
+ host_part = nm_strndup_a (200, self->host, s - self->host, &tmp_str);
+ if (nm_utils_parse_inaddr_bin (AF_INET6, host_part, &addr_family, &addrbin))
+ goto good;
+ }
+
+ return FALSE;
+
+good:
+ switch (addr_family) {
+ case AF_INET:
+ *((struct sockaddr_in *) sockaddr) = (struct sockaddr_in) {
+ .sin_family = AF_INET,
+ .sin_addr = addrbin.addr4_struct,
+ .sin_port = htons (self->port),
+ };
+ return TRUE;
+ case AF_INET6:
+ *((struct sockaddr_in6 *) sockaddr) = (struct sockaddr_in6) {
+ .sin6_family = AF_INET6,
+ .sin6_addr = addrbin.addr6,
+ .sin6_port = htons (self->port),
+ .sin6_scope_id = scope_id,
+ .sin6_flowinfo = 0,
+ };
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+/*****************************************************************************/
+
struct IsoLangToEncodings
{
const char *lang;
diff --git a/libnm-core/nm-utils.h b/libnm-core/nm-utils.h
index cf9572b907..34aae560e3 100644
--- a/libnm-core/nm-utils.h
+++ b/libnm-core/nm-utils.h
@@ -40,6 +40,8 @@
G_BEGIN_DECLS
+/*****************************************************************************/
+
typedef struct _NMVariantAttributeSpec NMVariantAttributeSpec;
/* SSID helpers */
diff --git a/libnm-core/tests/test-general.c b/libnm-core/tests/test-general.c
index 96eafa3a68..7d97296ab4 100644
--- a/libnm-core/tests/test-general.c
+++ b/libnm-core/tests/test-general.c
@@ -5522,6 +5522,158 @@ test_setting_user_data (void)
/*****************************************************************************/
+typedef union {
+ struct sockaddr sa;
+ struct sockaddr_in in;
+ struct sockaddr_in6 in6;
+} SockAddrUnion;
+
+static void
+_sock_addr_endpoint (const char *endpoint,
+ const char *host,
+ gint32 port)
+{
+ nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+ const char *s_endpoint;
+ const char *s_host;
+ gint32 s_port;
+ SockAddrUnion sockaddr = { };
+
+ g_assert (endpoint);
+ g_assert (!host == (port == -1));
+ g_assert (port >= -1 && port <= G_MAXUINT16);
+
+ ep = nm_sock_addr_endpoint_new (endpoint);
+ g_assert (ep);
+
+ s_endpoint = nm_sock_addr_endpoint_get_endpoint (ep);
+ s_host = nm_sock_addr_endpoint_get_host (ep);
+ s_port = nm_sock_addr_endpoint_get_port (ep);
+ g_assert_cmpstr (endpoint, ==, s_endpoint);
+ g_assert_cmpstr (host, ==, s_host);
+ g_assert_cmpint (port, ==, s_port);
+
+ g_assert (!nm_sock_addr_endpoint_get_fixed_sockaddr (ep, &sockaddr));
+
+ if (endpoint[0] != ' ') {
+ gs_free char *endpoint2 = NULL;
+
+ /* also test with a leading space */
+ endpoint2 = g_strdup_printf (" %s", endpoint);
+ _sock_addr_endpoint (endpoint2, host, port);
+ }
+
+ if (endpoint[0] && endpoint[strlen (endpoint) - 1] != ' ') {
+ gs_free char *endpoint2 = NULL;
+
+ /* also test with a trailing space */
+ endpoint2 = g_strdup_printf ("%s ", endpoint);
+ _sock_addr_endpoint (endpoint2, host, port);
+ }
+}
+
+static void
+_sock_addr_endpoint_fixed (const char *endpoint,
+ const char *host,
+ guint16 port,
+ guint scope_id)
+{
+ nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
+ const char *s_endpoint;
+ const char *s_host;
+ gint32 s_port;
+ int addr_family;
+ NMIPAddr addrbin;
+ SockAddrUnion sockaddr = { };
+
+ g_assert (endpoint);
+ g_assert (host);
+ g_assert (port > 0);
+
+ if (!nm_utils_parse_inaddr_bin (AF_UNSPEC, host, &addr_family, &addrbin))
+ g_assert_not_reached ();
+
+ ep = nm_sock_addr_endpoint_new (endpoint);
+ g_assert (ep);
+
+ s_endpoint = nm_sock_addr_endpoint_get_endpoint (ep);
+ s_host = nm_sock_addr_endpoint_get_host (ep);
+ s_port = nm_sock_addr_endpoint_get_port (ep);
+ g_assert_cmpstr (endpoint, ==, s_endpoint);
+ g_assert_cmpstr (NULL, !=, s_host);
+ g_assert_cmpint (port, ==, s_port);
+
+ if (!nm_sock_addr_endpoint_get_fixed_sockaddr (ep, &sockaddr))
+ g_assert_not_reached ();
+
+ g_assert_cmpint (sockaddr.sa.sa_family, ==, addr_family);
+ if (addr_family == AF_INET) {
+ const SockAddrUnion s = {
+ .in = {
+ .sin_family = AF_INET,
+ .sin_addr = addrbin.addr4_struct,
+ .sin_port = htons (port),
+ },
+ };
+
+ g_assert_cmpint (sockaddr.in.sin_addr.s_addr, ==, addrbin.addr4);
+ g_assert_cmpint (sockaddr.in.sin_port, ==, htons (port));
+ g_assert (memcmp (&s, &sockaddr, sizeof (s.in)) == 0);
+ } else if (addr_family == AF_INET6) {
+ const SockAddrUnion s = {
+ .in6 = {
+ .sin6_family = AF_INET6,
+ .sin6_addr = addrbin.addr6,
+ .sin6_scope_id = scope_id,
+ .sin6_port = htons (port),
+ },
+ };
+
+ g_assert (memcmp (&sockaddr.in6.sin6_addr, &addrbin, sizeof (addrbin.addr6)) == 0);
+ g_assert_cmpint (sockaddr.in6.sin6_port, ==, htons (port));
+ g_assert_cmpint (sockaddr.in6.sin6_scope_id, ==, scope_id);
+ g_assert_cmpint (sockaddr.in6.sin6_flowinfo, ==, 0);
+ g_assert (memcmp (&s, &sockaddr, sizeof (s.in6)) == 0);
+ } else
+ g_assert_not_reached ();
+}
+
+static void
+test_sock_addr_endpoint (void)
+{
+ _sock_addr_endpoint ("", NULL, -1);
+ _sock_addr_endpoint (":", NULL, -1);
+ _sock_addr_endpoint ("a", NULL, -1);
+ _sock_addr_endpoint ("a:", NULL, -1);
+ _sock_addr_endpoint (":a", NULL, -1);
+ _sock_addr_endpoint ("[]:a", NULL, -1);
+ _sock_addr_endpoint ("[]a", NULL, -1);
+ _sock_addr_endpoint ("[]:", NULL, -1);
+ _sock_addr_endpoint ("[a]b", NULL, -1);
+ _sock_addr_endpoint ("[a:b", NULL, -1);
+ _sock_addr_endpoint ("[a[:b", NULL, -1);
+ _sock_addr_endpoint ("a:6", "a", 6);
+ _sock_addr_endpoint ("a:6", "a", 6);
+ _sock_addr_endpoint ("[a]:6", "a", 6);
+ _sock_addr_endpoint ("[a]:6", "a", 6);
+ _sock_addr_endpoint ("[a]:655", "a", 655);
+ _sock_addr_endpoint ("[ab]:][6", NULL, -1);
+ _sock_addr_endpoint ("[ab]:]:[6", NULL, -1);
+ _sock_addr_endpoint ("[a[]:b", NULL, -1);
+ _sock_addr_endpoint ("[192.169.6.x]:6", "192.169.6.x", 6);
+ _sock_addr_endpoint ("[192.169.6.x]:0", NULL, -1);
+ _sock_addr_endpoint ("192.169.6.7:0", NULL, -1);
+
+ _sock_addr_endpoint_fixed ("192.169.6.7:6", "192.169.6.7", 6, 0);
+ _sock_addr_endpoint_fixed ("[192.169.6.7]:6", "192.169.6.7", 6, 0);
+ _sock_addr_endpoint_fixed ("[a:b::]:6", "a:b::", 6, 0);
+ _sock_addr_endpoint_fixed ("[a:b::%7]:6", "a:b::", 6, 7);
+ _sock_addr_endpoint_fixed ("a:b::1%75:6", "a:b::1", 6, 75);
+ _sock_addr_endpoint_fixed ("a:b::1%0:64", "a:b::1", 64, 0);
+}
+
+/*****************************************************************************/
+
static void
test_hexstr2bin (void)
{
@@ -7735,6 +7887,8 @@ int main (int argc, char **argv)
g_test_add_func ("/core/general/test_setting_compare_default_strv", test_setting_compare_default_strv);
g_test_add_func ("/core/general/test_setting_user_data", test_setting_user_data);
+ g_test_add_func ("/core/general/test_sock_addr_endpoint", test_sock_addr_endpoint);
+
g_test_add_func ("/core/general/hexstr2bin", test_hexstr2bin);
g_test_add_func ("/core/general/nm_strquote", test_nm_strquote);
g_test_add_func ("/core/general/test_nm_utils_uuid_generate_from_string", test_nm_utils_uuid_generate_from_string);