diff options
author | Thomas Haller <thaller@redhat.com> | 2018-12-29 13:01:28 +0100 |
---|---|---|
committer | Thomas Haller <thaller@redhat.com> | 2019-02-14 08:00:29 +0100 |
commit | 713e879d769f4aa95d1b15f5a584cc9cf4e9bd13 (patch) | |
tree | 7c7a94d5311b78bf6d8b3dd5b642dd67072c110d | |
parent | d93845e2c2c67349abcfc1f5377d820f02247b8b (diff) | |
download | NetworkManager-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.h | 19 | ||||
-rw-r--r-- | libnm-core/nm-utils.c | 271 | ||||
-rw-r--r-- | libnm-core/nm-utils.h | 2 | ||||
-rw-r--r-- | libnm-core/tests/test-general.c | 154 |
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); |