Commit 713e879d authored by Thomas Haller's avatar Thomas Haller

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).
parent d93845e2
......@@ -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;
......
......@@ -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;
......
......@@ -40,6 +40,8 @@
G_BEGIN_DECLS
/*****************************************************************************/
typedef struct _NMVariantAttributeSpec NMVariantAttributeSpec;
/* SSID helpers */
......
......@@ -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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment