NetworkManager/libnm-core/nm-setting-wireguard.c
Thomas Haller 70971d1141
all: avoid wrong compiler warning about uninitalized variables with LTO
Seems with LTO the compiler can sometimes think that thes variables are
uninitialized. Usually those code paths are only after an assertion was
hit (g_return*()), but we still need to workaround the warning.
2020-08-17 15:18:02 +02:00

2597 lines
78 KiB
C

// SPDX-License-Identifier: LGPL-2.1+
/*
* Copyright (C) 2018 - 2019 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-setting-wireguard.h"
#include "nm-setting-private.h"
#include "nm-utils-private.h"
#include "nm-connection-private.h"
#include "nm-glib-aux/nm-secret-utils.h"
/*****************************************************************************/
/**
* SECTION:nm-setting-wireguard
* @short_description: Describes connection properties for wireguard related options
*
* The #NMSettingWireGuard object is a #NMSetting subclass that contains settings
* for configuring WireGuard.
**/
/*****************************************************************************/
static NMWireGuardPeer *_wireguard_peer_dup (const NMWireGuardPeer *self);
G_DEFINE_BOXED_TYPE (NMWireGuardPeer, nm_wireguard_peer, _wireguard_peer_dup, nm_wireguard_peer_unref)
/* NMWireGuardPeer can also track invalid allowed-ip settings, and only reject
* them later during is_valid(). Such values are marked by a leading 'X' character
* in the @allowed_ips. It is expected, that such values are the exception, and
* commonly not present. */
#define ALLOWED_IP_INVALID_X 'X'
#define ALLOWED_IP_INVALID_X_STR "X"
/**
* NMWireGuardPeer:
*
* The settings of one WireGuard peer.
*
* Since: 1.16
*/
struct _NMWireGuardPeer {
NMSockAddrEndpoint *endpoint;
char *public_key;
char *preshared_key;
GPtrArray *allowed_ips;
guint refcount;
NMSettingSecretFlags preshared_key_flags;
guint16 persistent_keepalive;
bool public_key_valid:1;
bool preshared_key_valid:1;
bool sealed:1;
};
static gboolean
NM_IS_WIREGUARD_PEER (const NMWireGuardPeer *self, gboolean also_sealed)
{
return self
&& self->refcount > 0
&& ( also_sealed
|| !self->sealed);
}
/**
* nm_wireguard_peer_new:
*
* Returns: (transfer full): a new, default, unsealed #NMWireGuardPeer instance.
*
* Since: 1.16
*/
NMWireGuardPeer *
nm_wireguard_peer_new (void)
{
NMWireGuardPeer *self;
self = g_slice_new (NMWireGuardPeer);
*self = (NMWireGuardPeer) {
.refcount = 1,
.preshared_key_flags = NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
};
return self;
}
/**
* nm_wireguard_peer_new_clone:
* @self: the #NMWireGuardPeer instance to copy.
* @with_secrets: if %TRUE, the preshared-key secrets are copied
* as well. Otherwise, they will be removed.
*
* Returns: (transfer full): a clone of @self. This instance
* is always unsealed.
*
* Since: 1.16
*/
NMWireGuardPeer *
nm_wireguard_peer_new_clone (const NMWireGuardPeer *self,
gboolean with_secrets)
{
NMWireGuardPeer *new;
guint i;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
new = g_slice_new (NMWireGuardPeer);
*new = (NMWireGuardPeer) {
.refcount = 1,
.public_key = g_strdup (self->public_key),
.public_key_valid = self->public_key_valid,
.preshared_key = with_secrets ? g_strdup (self->preshared_key) : NULL,
.preshared_key_valid = self->preshared_key_valid,
.preshared_key_flags = self->preshared_key_flags,
.endpoint = nm_sock_addr_endpoint_ref (self->endpoint),
.persistent_keepalive = self->persistent_keepalive,
};
if ( self->allowed_ips
&& self->allowed_ips->len > 0) {
new->allowed_ips = g_ptr_array_new_full (self->allowed_ips->len,
g_free);
for (i = 0; i < self->allowed_ips->len; i++) {
g_ptr_array_add (new->allowed_ips,
g_strdup (self->allowed_ips->pdata[i]));
}
}
return new;
}
/**
* nm_wireguard_peer_ref:
* @self: (allow-none): the #NMWireGuardPeer instance
*
* This is not thread-safe.
*
* Returns: returns the input argument @self after incrementing
* the reference count.
*
* Since: 1.16
*/
NMWireGuardPeer *
nm_wireguard_peer_ref (NMWireGuardPeer *self)
{
if (!self)
return NULL;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
nm_assert (self->refcount < G_MAXUINT);
self->refcount++;
return self;
}
/**
* nm_wireguard_peer_unref:
* @self: (allow-none): the #NMWireGuardPeer instance
*
* Drop a reference to @self. If the last reference is dropped,
* the instance is freed and all associate data released.
*
* This is not thread-safe.
*
* Since: 1.16
*/
void
nm_wireguard_peer_unref (NMWireGuardPeer *self)
{
if (!self)
return;
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
if (--self->refcount > 0)
return;
nm_sock_addr_endpoint_unref (self->endpoint);
if (self->allowed_ips)
g_ptr_array_unref (self->allowed_ips);
g_free (self->public_key);
nm_free_secret (self->preshared_key);
g_slice_free (NMWireGuardPeer, self);
}
/**
* _wireguard_peer_dup:
* @self: the #NMWireGuardPeer instance
*
* Duplicates the #NMWireGuardPeer instance. Note that if @self
* is already sealed, this increments the reference count and
* returns it. If the instance is still unsealed, it is copied.
*
* Returns: (transfer full): a duplicate of @self, or (if the
* instance is sealed and thus immutable) a reference to @self.
* As such, the instance will be sealed if and only if @self is
* sealed.
*/
static NMWireGuardPeer *
_wireguard_peer_dup (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
if (self->sealed)
return nm_wireguard_peer_ref ((NMWireGuardPeer *) self);
return nm_wireguard_peer_new_clone (self, TRUE);
}
/**
* nm_wireguard_peer_seal:
* @self: the #NMWireGuardPeer instance
*
* Seal the #NMWireGuardPeer instance. Afterwards, it is a bug
* to call all functions that modify the instance (except ref/unref).
* A sealed instance cannot be unsealed again, but you can create
* an unsealed copy with nm_wireguard_peer_new_clone().
*
* Since: 1.16
*/
void
nm_wireguard_peer_seal (NMWireGuardPeer *self)
{
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE));
self->sealed = TRUE;
if (self->allowed_ips) {
if (self->allowed_ips->len == 0)
nm_clear_pointer (&self->allowed_ips, g_ptr_array_unref);
}
}
/**
* nm_wireguard_peer_is_sealed:
* @self: the #NMWireGuardPeer instance
*
* Returns: whether @self is sealed or not.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_is_sealed (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), FALSE);
return self->sealed;
}
/**
* nm_wireguard_peer_get_public_key:
* @self: the #NMWireGuardPeer instance
*
* Returns: (transfer none): the public key or %NULL if unset.
*
* Since: 1.16
*/
const char *
nm_wireguard_peer_get_public_key (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
return self->public_key;
}
/**
* nm_wireguard_peer_set_public_key:
* @self: the unsealed #NMWireGuardPeer instance
* @public_key: (allow-none) (transfer none): the new public
* key or %NULL to clear the public key.
* @accept_invalid: if %TRUE and @public_key is not %NULL and
* invalid, then do not modify the instance.
*
* Reset the public key. Note that if the public key is valid, it
* will be normalized (which may or may not modify the set value).
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the key was valid or %NULL. Returns
* %FALSE for invalid keys. Depending on @accept_invalid
* will an invalid key be set or not.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_set_public_key (NMWireGuardPeer *self,
const char *public_key,
gboolean accept_invalid)
{
char *public_key_normalized = NULL;
gboolean is_valid;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!public_key) {
nm_clear_g_free (&self->public_key);
return TRUE;
}
is_valid = nm_utils_base64secret_normalize (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized);
nm_assert (is_valid == (public_key_normalized != NULL));
if ( !is_valid
&& !accept_invalid)
return FALSE;
self->public_key_valid = is_valid;
g_free (self->public_key);
self->public_key = public_key_normalized ?: g_strdup (public_key);
return is_valid;
}
void
_nm_wireguard_peer_set_public_key_bin (NMWireGuardPeer *self,
const guint8 public_key[static NM_WIREGUARD_PUBLIC_KEY_LEN])
{
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
nm_clear_g_free (&self->public_key);
if (!public_key)
return;
self->public_key = g_base64_encode (public_key, NM_WIREGUARD_PUBLIC_KEY_LEN);
self->public_key_valid = TRUE;
}
/**
* nm_wireguard_peer_get_preshared_key:
* @self: the #NMWireGuardPeer instance
*
* Returns: (transfer none): the preshared key or %NULL if unset.
*
* Since: 1.16
*/
const char *
nm_wireguard_peer_get_preshared_key (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
return self->preshared_key;
}
/**
* nm_wireguard_peer_set_preshared_key:
* @self: the unsealed #NMWireGuardPeer instance
* @preshared_key: (allow-none) (transfer none): the new preshared
* key or %NULL to clear the preshared key.
* @accept_invalid: whether to allow setting the key to an invalid
* value. If %FALSE, @self is unchanged if the key is invalid
* and if %FALSE is returned.
*
* Reset the preshared key. Note that if the preshared key is valid, it
* will be normalized (which may or may not modify the set value).
*
* Note that the preshared-key is a secret and consequently has corresponding
* preshared-key-flags property. This is so that secrets can be optional
* and requested on demand from a secret-agent. Also, an invalid preshared-key
* may optionally cause nm_wireguard_peer_is_valid() to fail or it may
* be accepted.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the preshared-key is valid, otherwise %FALSE.
* %NULL is considered a valid value.
* If the key is invalid, it depends on @accept_invalid whether the
* previous value was reset.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_set_preshared_key (NMWireGuardPeer *self,
const char *preshared_key,
gboolean accept_invalid)
{
char *preshared_key_normalized = NULL;
gboolean is_valid;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!preshared_key) {
nm_clear_pointer (&self->preshared_key, nm_free_secret);
return TRUE;
}
is_valid = nm_utils_base64secret_normalize (preshared_key,
NM_WIREGUARD_SYMMETRIC_KEY_LEN,
&preshared_key_normalized);
nm_assert (is_valid == (preshared_key_normalized != NULL));
if ( !is_valid
&& !accept_invalid)
return FALSE;
self->preshared_key_valid = is_valid;
nm_free_secret (self->preshared_key);
self->preshared_key = preshared_key_normalized ?: g_strdup (preshared_key);
return is_valid;
}
/**
* nm_wireguard_peer_get_preshared_key_flags:
* @self: the #NMWireGuardPeer instance
*
* Returns: get the secret flags for the preshared-key.
*
* Since: 1.16
*/
NMSettingSecretFlags
nm_wireguard_peer_get_preshared_key_flags (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
return self->preshared_key_flags;
}
/**
* nm_wireguard_peer_set_preshared_key_flags:
* @self: the unsealed #NMWireGuardPeer instance
* @preshared_key_flags: the secret flags to set.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Since: 1.16
*/
void
nm_wireguard_peer_set_preshared_key_flags (NMWireGuardPeer *self,
NMSettingSecretFlags preshared_key_flags)
{
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
self->preshared_key_flags = preshared_key_flags;
}
/**
* nm_wireguard_peer_get_persistent_keepalive:
* @self: the #NMWireGuardPeer instance
*
* Returns: get the persistent-keepalive setting in seconds. Set to zero to disable
* keep-alive.
*
* Since: 1.16
*/
guint16
nm_wireguard_peer_get_persistent_keepalive (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
return self->persistent_keepalive;
}
/**
* nm_wireguard_peer_set_persistent_keepalive:
* @self: the unsealed #NMWireGuardPeer instance
* @persistent_keepalive: the keep-alive value to set.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Since: 1.16
*/
void
nm_wireguard_peer_set_persistent_keepalive (NMWireGuardPeer *self,
guint16 persistent_keepalive)
{
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
self->persistent_keepalive = persistent_keepalive;
}
NMSockAddrEndpoint *
_nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
return self->endpoint;
}
/**
* nm_wireguard_peer_get_endpoint:
* @self: the #NMWireGuardPeer instance
*
* Returns: (transfer none): the endpoint or %NULL if none was set.
*
* Since: 1.16
*/
const char *
nm_wireguard_peer_get_endpoint (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
return self->endpoint
? nm_sock_addr_endpoint_get_endpoint (self->endpoint)
: NULL;
}
void
_nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
NMSockAddrEndpoint *endpoint)
{
NMSockAddrEndpoint *old;
nm_assert (NM_IS_WIREGUARD_PEER (self, FALSE));
old = self->endpoint;
self->endpoint = nm_sock_addr_endpoint_ref (endpoint);
nm_sock_addr_endpoint_unref (old);
}
/**
* nm_wireguard_peer_set_endpoint:
* @self: the unsealed #NMWireGuardPeer instance
* @endpoint: the socket address endpoint to set or %NULL.
* @allow_invalid: if %TRUE, also invalid values are set.
* If %FALSE, the function does nothing for invalid @endpoint
* arguments.
*
* Sets or clears the endpoint of @self.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the endpoint is %NULL or valid. For an
* invalid @endpoint argument, %FALSE is returned. Depending
* on @allow_invalid, the instance will be modified.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_set_endpoint (NMWireGuardPeer *self,
const char *endpoint,
gboolean allow_invalid)
{
NMSockAddrEndpoint *old;
NMSockAddrEndpoint *new;
gboolean is_valid;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if (!endpoint) {
nm_clear_pointer (&self->endpoint, nm_sock_addr_endpoint_unref);
return TRUE;
}
new = nm_sock_addr_endpoint_new (endpoint);
is_valid = (nm_sock_addr_endpoint_get_host (new) != NULL);
if ( !allow_invalid
&& !is_valid) {
nm_sock_addr_endpoint_unref (new);
return FALSE;
}
old = self->endpoint;
self->endpoint = new;
nm_sock_addr_endpoint_unref (old);
return is_valid;
}
/**
* nm_wireguard_peer_get_allowed_ips_len:
* @self: the #NMWireGuardPeer instance
*
* Returns: the number of allowed-ips entries.
*
* Since: 1.16
*/
guint
nm_wireguard_peer_get_allowed_ips_len (const NMWireGuardPeer *self)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), 0);
return self->allowed_ips ? self->allowed_ips->len : 0u;
}
/**
* nm_wireguard_peer_get_allowed_ip:
* @self: the #NMWireGuardPeer instance
* @idx: the index from zero to (allowed-ips-len - 1) to
* retrieve.
* @out_is_valid: (allow-none): %TRUE if the returned value is a valid allowed-ip
* setting.
*
* Returns: (transfer none): the allowed-ip setting at index @idx.
* If @idx is out of range, %NULL will be returned.
*
* Since: 1.16
*/
const char *
nm_wireguard_peer_get_allowed_ip (const NMWireGuardPeer *self,
guint idx,
gboolean *out_is_valid)
{
const char *s;
/* With LTO, the compiler might warn about the g_return_val_if_fail()
* code path not initializing the output argument. Workaround that by
* always setting the out argument. */
NM_SET_OUT (out_is_valid, FALSE);
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), NULL);
if ( !self->allowed_ips
|| idx >= self->allowed_ips->len)
return NULL;
s = self->allowed_ips->pdata[idx];
NM_SET_OUT (out_is_valid, s[0] != ALLOWED_IP_INVALID_X);
return s[0] == ALLOWED_IP_INVALID_X ? &s[1] : s;
}
/**
* nm_wireguard_peer_clear_allowed_ips:
* @self: the unsealed #NMWireGuardPeer instance
*
* Removes all allowed-ip entries.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Since: 1.16
*/
void
nm_wireguard_peer_clear_allowed_ips (NMWireGuardPeer *self)
{
g_return_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE));
if (self->allowed_ips)
g_ptr_array_set_size (self->allowed_ips, 0);
}
static gboolean
_peer_append_allowed_ip (NMWireGuardPeer *self,
const char *allowed_ip,
gboolean accept_invalid)
{
int addr_family;
int prefix;
NMIPAddr addrbin;
char *str;
gboolean is_valid = TRUE;
nm_assert (NM_IS_WIREGUARD_PEER (self, FALSE));
nm_assert (allowed_ip);
/* normalize the address (if it is valid. Otherwise, take it
* as-is (it will render the instance invalid). */
if (!nm_utils_parse_inaddr_prefix_bin (AF_UNSPEC,
allowed_ip,
&addr_family,
&addrbin,
&prefix)) {
if (!accept_invalid)
return FALSE;
/* mark the entry as invalid by having a "X" prefix. */
str = g_strconcat (ALLOWED_IP_INVALID_X_STR, allowed_ip, NULL);
is_valid = FALSE;
} else {
char addrstr[NM_UTILS_INET_ADDRSTRLEN];
nm_assert_addr_family (addr_family);
nm_utils_inet_ntop (addr_family, &addrbin, addrstr);
if (prefix >= 0)
str = g_strdup_printf ("%s/%d", addrstr, prefix);
else
str = g_strdup (addrstr);
nm_assert (str[0] != ALLOWED_IP_INVALID_X);
}
if (!self->allowed_ips)
self->allowed_ips = g_ptr_array_new_with_free_func (g_free);
g_ptr_array_add (self->allowed_ips, str);
return is_valid;
}
/**
* nm_wireguard_peer_append_allowed_ip:
* @self: the unsealed #NMWireGuardPeer instance
* @allowed_ip: the allowed-ip entry to set.
* @accept_invalid: if %TRUE, also invalid @allowed_ip value
* will be appended. Otherwise, the function does nothing
* in face of invalid values and returns %FALSE.
*
* Appends @allowed_ip setting to the list. This does not check
* for duplicates and always appends @allowed_ip to the end of the
* list. If @allowed_ip is valid, it will be normalized and a modified
* for might be appended. If @allowed_ip is invalid, it will still be
* appended, but later verification will fail.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if the value is a valid allowed-ips value, %FALSE otherwise.
* Depending on @accept_invalid, also invalid values are added.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_append_allowed_ip (NMWireGuardPeer *self,
const char *allowed_ip,
gboolean accept_invalid)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
g_return_val_if_fail (allowed_ip, FALSE);
return _peer_append_allowed_ip (self, allowed_ip, accept_invalid);
}
/**
* nm_wireguard_peer_remove_allowed_ip:
* @self: the unsealed #NMWireGuardPeer instance
* @idx: the index from zero to (allowed-ips-len - 1) to
* retrieve. If the index is out of range, %FALSE is returned
* and nothing is done.
*
* Removes the allowed-ip at the given @idx. This shifts all
* following entries one index down.
*
* It is a bug trying to modify a sealed #NMWireGuardPeer instance.
*
* Returns: %TRUE if @idx was valid and the allowed-ip was removed.
* %FALSE otherwise, and the peer will not be changed.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_remove_allowed_ip (NMWireGuardPeer *self,
guint idx)
{
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, FALSE), FALSE);
if ( !self->allowed_ips
|| idx >= self->allowed_ips->len)
return FALSE;
g_ptr_array_remove_index (self->allowed_ips, idx);
return TRUE;
}
/**
* nm_wireguard_peer_is_valid:
* @self: the #NMWireGuardPeer instance
* @check_secrets: if %TRUE, non-secret properties are validated.
* Otherwise, they are ignored for this purpose.
* @check_non_secrets: if %TRUE, secret properties are validated.
* Otherwise, they are ignored for this purpose.
* @error: the #GError location for returning the failure reason.
*
* Returns: %TRUE if the peer is valid or fails with an error
* reason.
*
* Since: 1.16
*/
gboolean
nm_wireguard_peer_is_valid (const NMWireGuardPeer *self,
gboolean check_non_secrets,
gboolean check_secrets,
GError **error)
{
guint i;
g_return_val_if_fail (NM_IS_WIREGUARD_PEER (self, TRUE), FALSE);
g_return_val_if_fail (!error || !*error, FALSE);
if (check_non_secrets) {
if (!self->public_key) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("missing public-key for peer"));
return FALSE;
} else if (!self->public_key_valid) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid public-key for peer"));
return FALSE;
}
}
if (check_secrets) {
if ( self->preshared_key
&& !self->preshared_key_valid) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid preshared-key for peer"));
return FALSE;
}
}
if (check_non_secrets) {
if (!_nm_utils_secret_flags_validate (self->preshared_key_flags,
NULL,
NULL,
NM_SETTING_SECRET_FLAG_NONE,
error))
return FALSE;
}
if (check_non_secrets) {
if ( self->endpoint
&& !nm_sock_addr_endpoint_get_host (self->endpoint)) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid endpoint for peer"));
return FALSE;
}
if (self->allowed_ips) {
for (i = 0; i < self->allowed_ips->len; i++) {
const char *s = self->allowed_ips->pdata[i];
if (s[0] == ALLOWED_IP_INVALID_X) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid IP address \"%s\" for allowed-ip of peer"),
&s[1]);
return FALSE;
}
}
}
if (!_nm_setting_secret_flags_valid (self->preshared_key_flags)) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("invalid preshared-key-flags for peer"));
return FALSE;
}
}
return TRUE;
}
/**
* nm_wireguard_peer_cmp:
* @a: (allow-none): the #NMWireGuardPeer to compare.
* @b: (allow-none): the other #NMWireGuardPeer to compare.
* @compare_flags: #NMSettingCompareFlags to affect the comparison.
*
* Returns: zero of the two instances are equivalent or
* a non-zero integer otherwise. This defines a total ordering
* over the peers. Whether a peer is sealed or not, does not
* affect the comparison.
*
* Since: 1.16
*/
int
nm_wireguard_peer_cmp (const NMWireGuardPeer *a,
const NMWireGuardPeer *b,
NMSettingCompareFlags compare_flags)
{
guint i, n;
NM_CMP_SELF (a, b);
/* regardless of the @compare_flags, the public-key is the ID of the peer. It must
* always be compared. */
NM_CMP_FIELD_BOOL (a, b, public_key_valid);
NM_CMP_FIELD_STR0 (a, b, public_key);
if (NM_FLAGS_ANY (compare_flags, NM_SETTING_COMPARE_FLAG_INFERRABLE
| NM_SETTING_COMPARE_FLAG_FUZZY))
return 0;
NM_CMP_FIELD_BOOL (a, b, endpoint);
if (a->endpoint) {
NM_CMP_DIRECT_STRCMP0 (nm_sock_addr_endpoint_get_endpoint (a->endpoint),
nm_sock_addr_endpoint_get_endpoint (b->endpoint));
}
NM_CMP_FIELD (a, b, persistent_keepalive);
NM_CMP_DIRECT ((n = (a->allowed_ips ? a->allowed_ips->len : 0u)),
( b->allowed_ips ? b->allowed_ips->len : 0u ));
for (i = 0; i < n; i++)
NM_CMP_DIRECT_STRCMP0 (a->allowed_ips->pdata[i], b->allowed_ips->pdata[i]);
NM_CMP_FIELD (a, b, preshared_key_flags);
if (!NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS)) {
if ( NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_AGENT_OWNED_SECRETS)
&& NM_FLAGS_HAS (a->preshared_key_flags, NM_SETTING_SECRET_FLAG_AGENT_OWNED)) {
/* pass */
} else if ( NM_FLAGS_HAS (compare_flags, NM_SETTING_COMPARE_FLAG_IGNORE_NOT_SAVED_SECRETS)
&& NM_FLAGS_HAS (a->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_SAVED)) {
/* pass */
} else {
NM_CMP_FIELD_BOOL (a, b, preshared_key_valid);
NM_CMP_FIELD_STR0 (a, b, preshared_key);
}
}
return 0;
}
/*****************************************************************************/
typedef struct {
const char *public_key;
NMWireGuardPeer *peer;
guint idx;
} PeerData;
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE_BASE (
PROP_FWMARK,
PROP_IP4_AUTO_DEFAULT_ROUTE,
PROP_IP6_AUTO_DEFAULT_ROUTE,
PROP_LISTEN_PORT,
PROP_MTU,
PROP_PEER_ROUTES,
PROP_PRIVATE_KEY,
PROP_PRIVATE_KEY_FLAGS,
);
typedef struct {
char *private_key;
GPtrArray *peers_arr;
GHashTable *peers_hash;
NMSettingSecretFlags private_key_flags;
NMTernary ip4_auto_default_route;
NMTernary ip6_auto_default_route;
guint32 fwmark;
guint32 mtu;
guint16 listen_port;
bool private_key_valid:1;
bool peer_routes:1;
} NMSettingWireGuardPrivate;
/**
* NMSettingWireGuard:
*
* WireGuard Settings
*
* Since: 1.16
*/
struct _NMSettingWireGuard {
NMSetting parent;
NMSettingWireGuardPrivate _priv;
};
struct _NMSettingWireGuardClass {
NMSettingClass parent;
};
G_DEFINE_TYPE (NMSettingWireGuard, nm_setting_wireguard, NM_TYPE_SETTING)
#define NM_SETTING_WIREGUARD_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMSettingWireGuard, NM_IS_SETTING_WIREGUARD, NMSetting)
/*****************************************************************************/
#define peers_psk_get_secret_name_a(public_key, to_free) \
nm_construct_name_a (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key), (to_free))
#define peers_psk_get_secret_name_dup(public_key) \
g_strdup_printf (NM_SETTING_WIREGUARD_PEERS".%s."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, (public_key))
#define peers_psk_get_secret_parse_a(secret_public_key, public_key_free) \
({ \
const char *_secret_public_key = (secret_public_key); \
char **_public_key_free = (public_key_free); \
const char *_public_key = NULL; \
\
nm_assert (_public_key_free && !*_public_key_free); \
\
if (NM_STR_HAS_PREFIX (_secret_public_key, NM_SETTING_WIREGUARD_PEERS".")) { \
_secret_public_key += NM_STRLEN (NM_SETTING_WIREGUARD_PEERS"."); \
if (NM_STR_HAS_SUFFIX (_secret_public_key, "."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) { \
_public_key = nm_strndup_a (300, _secret_public_key, strlen (_secret_public_key) - NM_STRLEN ("."NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY), _public_key_free); \
} \
} \
\
_public_key; \
})
/*****************************************************************************/
/**
* nm_setting_wireguard_get_private_key:
* @self: the #NMSettingWireGuard instance
*
* Returns: (transfer none): the set private-key or %NULL.
*
* Since: 1.16
*/
const char *
nm_setting_wireguard_get_private_key (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key;
}
/**
* nm_setting_wireguard_get_private_key_flags:
* @self: the #NMSettingWireGuard instance
*
* Returns: the secret-flags for #NMSettingWireGuard:private-key.
*
* Since: 1.16
*/
NMSettingSecretFlags
nm_setting_wireguard_get_private_key_flags (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->private_key_flags;
}
/**
* nm_setting_wireguard_get_fwmark:
* @self: the #NMSettingWireGuard instance
*
* Returns: the set firewall mark.
*
* Since: 1.16
*/
guint32
nm_setting_wireguard_get_fwmark (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->fwmark;
}
/**
* nm_setting_wireguard_get_listen_port:
* @self: the #NMSettingWireGuard instance
*
* Returns: the set UDP listen port.
*
* Since: 1.16
*/
guint16
nm_setting_wireguard_get_listen_port (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->listen_port;
}
/**
* nm_setting_wireguard_get_peer_routes:
* @self: the #NMSettingWireGuard instance
*
* Returns: whether automatically add peer routes.
*
* Since: 1.16
*/
gboolean
nm_setting_wireguard_get_peer_routes (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), TRUE);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->peer_routes;
}
/**
* nm_setting_wireguard_get_mtu:
* @self: the #NMSettingWireGuard instance
*
* Returns: the MTU of the setting.
*
* Since: 1.16
*/
guint32
nm_setting_wireguard_get_mtu (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->mtu;
}
/**
* nm_setting_wireguard_get_ip4_auto_default_route:
* @self: the #NMSettingWireGuard setting.
*
* Returns: the "ip4-auto-default-route" property of the setting.
*
* Since: 1.20
*/
NMTernary
nm_setting_wireguard_get_ip4_auto_default_route (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NM_TERNARY_DEFAULT);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->ip4_auto_default_route;
}
/**
* nm_setting_wireguard_get_ip6_auto_default_route:
* @self: the #NMSettingWireGuard setting.
*
* Returns: the "ip6-auto-default-route" property of the setting.
*
* Since: 1.20
*/
NMTernary
nm_setting_wireguard_get_ip6_auto_default_route (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NM_TERNARY_DEFAULT);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->ip6_auto_default_route;
}
/*****************************************************************************/
static void
_peer_free (PeerData *pd)
{
nm_assert (pd);
nm_wireguard_peer_unref (pd->peer);
g_slice_free (PeerData, pd);
}
/*****************************************************************************/
static void
_peers_notify (gpointer self)
{
_nm_setting_emit_property_changed (self);
}
static PeerData *
_peers_get (NMSettingWireGuardPrivate *priv,
guint idx)
{
PeerData *pd;
nm_assert (priv);
nm_assert (idx < priv->peers_arr->len);
pd = priv->peers_arr->pdata[idx];
nm_assert (pd);
nm_assert (pd->idx == idx);
nm_assert (NM_IS_WIREGUARD_PEER (pd->peer, TRUE));
nm_assert (nm_wireguard_peer_is_sealed (pd->peer));
nm_assert (pd->public_key == nm_wireguard_peer_get_public_key (pd->peer));
nm_assert (g_hash_table_lookup (priv->peers_hash, pd) == pd);
return pd;
}
static PeerData *
_peers_get_by_public_key (NMSettingWireGuardPrivate *priv,
const char *public_key,
gboolean try_with_normalized_key)
{
gs_free char *public_key_normalized = NULL;
PeerData *pd;
again:
nm_assert (priv);
nm_assert (public_key);
pd = g_hash_table_lookup (priv->peers_hash, &public_key);
if (pd) {
nm_assert (_peers_get (priv, pd->idx) == pd);
return pd;
}
if ( try_with_normalized_key
&& nm_utils_base64secret_normalize (public_key,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&public_key_normalized)) {
public_key = public_key_normalized;
try_with_normalized_key = FALSE;
goto again;
}
return NULL;
}
static void
_peers_remove (NMSettingWireGuardPrivate *priv,
PeerData *pd,
gboolean do_free)
{
guint i;
nm_assert (pd);
nm_assert (_peers_get (priv, pd->idx) == pd);
for (i = pd->idx + 1; i < priv->peers_arr->len; i++)
_peers_get (priv, i)->idx--;
g_ptr_array_remove_index (priv->peers_arr, pd->idx);
if (!g_hash_table_remove (priv->peers_hash, pd))
nm_assert_not_reached ();
if (do_free)
_peer_free (pd);
}
/**
* nm_setting_wireguard_get_peers_len:
* @self: the #NMSettingWireGuard instance
*
* Returns: the number of registered peers.
*
* Since: 1.16
*/
guint
nm_setting_wireguard_get_peers_len (NMSettingWireGuard *self)
{
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
return NM_SETTING_WIREGUARD_GET_PRIVATE (self)->peers_arr->len;
}
/**
* nm_setting_wireguard_get_peer:
* @self: the #NMSettingWireGuard instance
* @idx: the index to lookup.
*
* Returns: (transfer none): the #NMWireGuardPeer entry at
* index @idx. If the index is out of range, %NULL is returned.
*
* Since: 1.16
*/
NMWireGuardPeer *
nm_setting_wireguard_get_peer (NMSettingWireGuard *self,
guint idx)
{
NMSettingWireGuardPrivate *priv;
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
if (idx >= priv->peers_arr->len)
return NULL;
return _peers_get (priv, idx)->peer;
}
/**
* nm_setting_wireguard_get_peer_by_public_key:
* @self: the #NMSettingWireGuard instance
* @public_key: the public key for looking up the
* peer.
* @out_idx: (out) (allow-none): optional output argument
* for the index of the found peer. If no index is found,
* this is set to the nm_setting_wireguard_get_peers_len().
*
* Returns: (transfer none): the #NMWireGuardPeer instance with a
* matching public key. If no such peer exists, %NULL is returned.
*
* Since: 1.16
*/
NMWireGuardPeer *
nm_setting_wireguard_get_peer_by_public_key (NMSettingWireGuard *self,
const char *public_key,
guint *out_idx)
{
NMSettingWireGuardPrivate *priv;
PeerData *pd;
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), NULL);
g_return_val_if_fail (public_key, NULL);
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
pd = _peers_get_by_public_key (priv, public_key, TRUE);
if (!pd) {
NM_SET_OUT (out_idx, priv->peers_arr->len);
return NULL;
}
NM_SET_OUT (out_idx, pd->idx);
return pd->peer;
}
static gboolean
_peers_set (NMSettingWireGuardPrivate *priv,
NMWireGuardPeer *peer,
guint idx,
gboolean check_same_key)
{
PeerData *pd_same_key = NULL;
PeerData *pd_idx = NULL;
const char *public_key;
nm_assert (idx <= priv->peers_arr->len);
public_key = nm_wireguard_peer_get_public_key (peer);
if (idx < priv->peers_arr->len) {
pd_idx = _peers_get (priv, idx);
if (pd_idx->peer == peer)
return FALSE;
if ( check_same_key
&& nm_streq (public_key, nm_wireguard_peer_get_public_key (pd_idx->peer)))
check_same_key = FALSE;
}
nm_wireguard_peer_seal (peer);
nm_wireguard_peer_ref (peer);
if (check_same_key) {
pd_same_key = _peers_get_by_public_key (priv, public_key, FALSE);
if (pd_same_key) {
if (pd_idx) {
nm_assert (pd_same_key != pd_idx);
_peers_remove (priv, pd_same_key, TRUE);
pd_same_key = NULL;
} else {
if ( pd_same_key->peer == peer
&& pd_same_key->idx == priv->peers_arr->len - 1) {
nm_wireguard_peer_unref (peer);
return FALSE;
}
_peers_remove (priv, pd_same_key, FALSE);
nm_wireguard_peer_unref (pd_same_key->peer);
}
}
} else
nm_assert (_peers_get_by_public_key (priv, public_key, FALSE) == pd_idx);
if (pd_idx) {
g_hash_table_remove (priv->peers_hash, pd_idx);
nm_wireguard_peer_unref (pd_idx->peer);
pd_idx->public_key = public_key;
pd_idx->peer = peer;
g_hash_table_add (priv->peers_hash, pd_idx);
return TRUE;
}
if (!pd_same_key)
pd_same_key = g_slice_new (PeerData);
*pd_same_key = (PeerData) {
.peer = peer,
.public_key = public_key,
.idx = priv->peers_arr->len,
};
g_ptr_array_add (priv->peers_arr, pd_same_key);
if (!nm_g_hash_table_add (priv->peers_hash, pd_same_key))
nm_assert_not_reached ();
nm_assert (_peers_get (priv, pd_same_key->idx) == pd_same_key);
return TRUE;
}
static gboolean
_peers_append (NMSettingWireGuardPrivate *priv,
NMWireGuardPeer *peer,
gboolean check_same_key)
{
return _peers_set (priv, peer, priv->peers_arr->len, check_same_key);
}
/**
* nm_setting_wireguard_set_peer:
* @self: the #NMSettingWireGuard instance
* @peer: the #NMWireGuardPeer instance to set.
* This seals @peer and keeps a reference on the
* instance.
* @idx: the index, in the range of 0 to the number of
* peers (including). That means, if @idx is one past
* the end of the number of peers, this is the same as
* nm_setting_wireguard_append_peer(). Otherwise, the
* peer at this index is replaced.
*
* If @idx is one past the last peer, the behavior is the same
* as nm_setting_wireguard_append_peer().
* Otherwise, the peer will be at @idx and replace the peer
* instance at that index. Note that if a peer with the same
* public-key exists on another index, then that peer will also
* be replaced. In that case, the number of peers will shrink
* by one (because the one at @idx got replace and then one
* with the same public-key got removed). This also means,
* that the resulting index afterwards may be one less than
* @idx (if another peer with a lower index was dropped).
*
* Since: 1.16
*/
void
nm_setting_wireguard_set_peer (NMSettingWireGuard *self,
NMWireGuardPeer *peer,
guint idx)
{
NMSettingWireGuardPrivate *priv;
g_return_if_fail (NM_IS_SETTING_WIREGUARD (self));
g_return_if_fail (NM_IS_WIREGUARD_PEER (peer, TRUE));
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
g_return_if_fail (idx <= priv->peers_arr->len);
if (_peers_set (priv, peer, idx, TRUE))
_peers_notify (self);
}
/**
* nm_setting_wireguard_append_peer:
* @self: the #NMSettingWireGuard instance
* @peer: the #NMWireGuardPeer instance to append.
* This seals @peer and keeps a reference on the
* instance.
*
* If a peer with the same public-key already exists, that
* one is replaced by @peer. The new @peer is always appended
* (or moved to) the end, so in case a peer is replaced, the
* indexes are shifted and the number of peers stays unchanged.
*
* Since: 1.16
*/
void
nm_setting_wireguard_append_peer (NMSettingWireGuard *self,
NMWireGuardPeer *peer)
{
g_return_if_fail (NM_IS_SETTING_WIREGUARD (self));
g_return_if_fail (NM_IS_WIREGUARD_PEER (peer, TRUE));
if (_peers_append (NM_SETTING_WIREGUARD_GET_PRIVATE (self),
peer,
TRUE))
_peers_notify (self);
}
/**
* nm_setting_wireguard_remove_peer
* @self: the #NMSettingWireGuard instance
* @idx: the index to remove.
*
* Returns: %TRUE if @idx was in range and a peer
* was removed. Otherwise, @self is unchanged.
*
* Since: 1.16
*/
gboolean
nm_setting_wireguard_remove_peer (NMSettingWireGuard *self,
guint idx)
{
NMSettingWireGuardPrivate *priv;
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), FALSE);
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
if (idx >= priv->peers_arr->len)
return FALSE;
_peers_remove (priv, _peers_get (priv, idx), TRUE);
_peers_notify (self);
return TRUE;
}
static guint
_peers_clear (NMSettingWireGuardPrivate *priv)
{
guint l;
l = priv->peers_arr->len;
while (priv->peers_arr->len > 0) {
_peers_remove (priv,
_peers_get (priv, priv->peers_arr->len - 1),
TRUE);
}
return l;
}
/**
* nm_setting_wireguard_:
* @self: the #NMSettingWireGuard instance
*
* Returns: the number of cleared peers.
*
* Since: 1.16
*/
guint
nm_setting_wireguard_clear_peers (NMSettingWireGuard *self)
{
guint l;
g_return_val_if_fail (NM_IS_SETTING_WIREGUARD (self), 0);
l = _peers_clear (NM_SETTING_WIREGUARD_GET_PRIVATE (self));
if (l > 0)
_peers_notify (self);
return l;
}
/*****************************************************************************/
static GVariant *
_peers_dbus_only_synth (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *connection,
NMSetting *setting,
NMConnectionSerializationFlags flags,
const NMConnectionSerializationOptions *options)
{
NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
NMSettingWireGuardPrivate *priv;
gboolean any_peers = FALSE;
GVariantBuilder peers_builder;
guint i_peer, n_peers;
guint i;
n_peers = nm_setting_wireguard_get_peers_len (self);
if (n_peers == 0)
return NULL;
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
for (i_peer = 0; i_peer < n_peers; i_peer++) {
const NMWireGuardPeer *peer = _peers_get (priv, i_peer)->peer;
GVariantBuilder builder;
if (!peer->public_key)
continue;
g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, g_variant_new_string (peer->public_key));
if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
&& peer->endpoint)
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_ENDPOINT, g_variant_new_string (nm_sock_addr_endpoint_get_endpoint (peer->endpoint)));
if ( _nm_connection_serialize_secrets (flags, peer->preshared_key_flags)
&& peer->preshared_key)
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, g_variant_new_string (peer->preshared_key));
if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
&& peer->preshared_key_flags != NM_SETTING_SECRET_FLAG_NOT_REQUIRED)
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, g_variant_new_uint32 (peer->preshared_key_flags));
if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
&& peer->persistent_keepalive != 0)
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, g_variant_new_uint32 (peer->persistent_keepalive));
if ( !NM_FLAGS_HAS (flags, NM_CONNECTION_SERIALIZE_ONLY_SECRETS)
&& peer->allowed_ips
&& peer->allowed_ips->len > 0) {
const char *const*strv = (const char *const*) peer->allowed_ips->pdata;
gs_free const char **strv_fixed = NULL;
for (i = 0; i < peer->allowed_ips->len; i++) {
if (strv[i][0] != ALLOWED_IP_INVALID_X)
continue;
if (!strv_fixed) {
strv_fixed = nm_memdup (strv, sizeof (strv[0]) * peer->allowed_ips->len);
strv = strv_fixed;
}
((const char **) strv)[i]++;
}
g_variant_builder_add (&builder, "{sv}", NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS,
g_variant_new_strv (strv, peer->allowed_ips->len));
}
if (!any_peers) {
g_variant_builder_init (&peers_builder, G_VARIANT_TYPE ("aa{sv}"));
any_peers = TRUE;
}
g_variant_builder_add (&peers_builder, "a{sv}", &builder);
}
return any_peers
? g_variant_builder_end (&peers_builder)
: NULL;
}
static gboolean
_peers_dbus_only_set (NMSetting *setting,
GVariant *connection_dict,
const char *property,
GVariant *value,
NMSettingParseFlags parse_flags,
GError **error)
{
GVariantIter iter_peers;
GVariant *peer_var;
guint i_peer;
gboolean success = FALSE;
gboolean peers_changed = FALSE;
nm_assert (g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}")));
g_variant_iter_init (&iter_peers, value);
i_peer = 0;
while (g_variant_iter_next (&iter_peers, "@a{sv}", &peer_var)) {
_nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
const char *cstr;
guint32 u32;
GVariant *var;
i_peer++;
if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u has no public-key"),
i_peer);
goto out;
}
continue;
}
peer = nm_wireguard_peer_new ();
if (!nm_wireguard_peer_set_public_key (peer, cstr, TRUE)) {
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u has invalid public-key"),
i_peer);
goto out;
}
continue;
}
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_ENDPOINT, "&s", &cstr)) {
nm_auto_unref_sockaddrendpoint NMSockAddrEndpoint *ep = NULL;
ep = nm_sock_addr_endpoint_new (cstr);
if (!nm_sock_addr_endpoint_get_host (ep)) {
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u has invalid endpoint"),
i_peer);
goto out;
}
} else
_nm_wireguard_peer_set_endpoint (peer, ep);
}
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr))
nm_wireguard_peer_set_preshared_key (peer, cstr, TRUE);
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY_FLAGS, "u", &u32))
nm_wireguard_peer_set_preshared_key_flags (peer, u32);
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PERSISTENT_KEEPALIVE, "u", &u32))
nm_wireguard_peer_set_persistent_keepalive (peer, u32);
if (g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_ALLOWED_IPS, "@as", &var)) {
_nm_unused gs_unref_variant GVariant *var_free = var;
gs_free const char **allowed_ips = NULL;
gsize i, l;
allowed_ips = g_variant_get_strv (var, &l);
if (allowed_ips) {
for (i = 0; i < l; i++) {
if (_peer_append_allowed_ip (peer, allowed_ips[i], FALSE))
continue;
if (!NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT))
continue;
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u has invalid allowed-ips setting"),
i_peer);
goto out;
}
}
}
if (NM_FLAGS_HAS (parse_flags, NM_SETTING_PARSE_FLAGS_STRICT)) {
gs_free_error GError *local = NULL;
if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, &local)) {
g_set_error (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_MISSING_PROPERTY,
_("peer #%u is invalid: %s"),
i_peer, local->message);
goto out;
}
}
/* we could easily reject duplicate peers (by public-key) or duplicate GVariant attributes.
* However, don't do that. In case of duplicate values, the latter peer overwrite the earlier
* and GVariant attributes are ignored by g_variant_lookup() above. */
if (_peers_append (NM_SETTING_WIREGUARD_GET_PRIVATE (setting),
peer,
TRUE))
peers_changed = TRUE;
}
success = TRUE;
out:
if (peers_changed)
_peers_notify (setting);
return success;
}
/*****************************************************************************/
static gboolean
verify (NMSetting *setting, NMConnection *connection, GError **error)
{
NMSettingWireGuard *s_wg = NM_SETTING_WIREGUARD (setting);
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
guint i;
if (!_nm_connection_verify_required_interface_name (connection, error))
return FALSE;
if (!_nm_utils_secret_flags_validate (nm_setting_wireguard_get_private_key_flags (s_wg),
NM_SETTING_WIREGUARD_SETTING_NAME,
NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS,
NM_SETTING_SECRET_FLAG_NOT_REQUIRED,
error))
return FALSE;
for (i = 0; i < priv->peers_arr->len; i++) {
NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
if (!nm_wireguard_peer_is_valid (peer, TRUE, FALSE, error)) {
g_prefix_error (error,
"%s.%s[%u]: ",
NM_SETTING_WIREGUARD_SETTING_NAME,
NM_SETTING_WIREGUARD_PEERS,
i);
return FALSE;
}
}
if (connection) {
NMSettingIPConfig *s_ip4;
NMSettingIPConfig *s_ip6;
const char *method;
/* WireGuard is Layer 3 only. For the moment, we only support a restricted set of
* IP methods. We may relax that later, once we fix the implementations so they
* actually work. */
if ( (s_ip4 = nm_connection_get_setting_ip4_config (connection))
&& (method = nm_setting_ip_config_get_method (s_ip4))
&& !NM_IN_STRSET (method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED,
NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("method \"%s\" is not supported for WireGuard"),
method);
g_prefix_error (error, "%s.%s: ", NM_SETTING_IP4_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
return FALSE;
}
if ( (s_ip6 = nm_connection_get_setting_ip6_config (connection))
&& (method = nm_setting_ip_config_get_method (s_ip6))
&& !NM_IN_STRSET (method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL,
NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("method \"%s\" is not supported for WireGuard"),
method);
g_prefix_error (error, "%s.%s: ", NM_SETTING_IP6_CONFIG_SETTING_NAME, NM_SETTING_IP_CONFIG_METHOD);
return FALSE;
}
}
/* private-key is a secret, hence we cannot verify it like a regular property. */
return TRUE;
}
static gboolean
verify_secrets (NMSetting *setting, NMConnection *connection, GError **error)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
guint i;
if ( priv->private_key
&& !priv->private_key_valid) {
g_set_error_literal (error, NM_CONNECTION_ERROR, NM_CONNECTION_ERROR_INVALID_PROPERTY,
_("key must be 32 bytes base64 encoded"));
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PRIVATE_KEY);
return FALSE;
}
for (i = 0; i < priv->peers_arr->len; i++) {
NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
if (!nm_wireguard_peer_is_valid (peer, FALSE, TRUE, error)) {
g_prefix_error (error,
"%s.%s[%u]: ",
NM_SETTING_WIREGUARD_SETTING_NAME,
NM_SETTING_WIREGUARD_PEERS,
i);
return FALSE;
}
}
return TRUE;
}
static GPtrArray *
need_secrets (NMSetting *setting)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
GPtrArray *secrets = NULL;
guint i;
if ( !priv->private_key
|| !priv->private_key_valid) {
secrets = g_ptr_array_new_full (1, g_free);
g_ptr_array_add (secrets, g_strdup (NM_SETTING_WIREGUARD_PRIVATE_KEY));
}
for (i = 0; i < priv->peers_arr->len; i++) {
NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
if (NM_FLAGS_HAS (peer->preshared_key_flags, NM_SETTING_SECRET_FLAG_NOT_REQUIRED))
continue;
if (peer->preshared_key_valid)
continue;
if (!peer->public_key_valid)
continue;
if (!secrets)
secrets = g_ptr_array_new_full (1, g_free);
g_ptr_array_add (secrets, peers_psk_get_secret_name_dup (peer->public_key));
}
return secrets;
}
static gboolean
clear_secrets (const NMSettInfoSetting *sett_info,
guint property_idx,
NMSetting *setting,
NMSettingClearSecretsWithFlagsFn func,
gpointer user_data)
{
if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
gboolean peers_changed = FALSE;
guint i, j;
j = 0;
for (i = 0; i < priv->peers_arr->len; i++) {
NMWireGuardPeer *peer = _peers_get (priv, i)->peer;
if (!peer->preshared_key)
continue;
if (func) {
gs_free char *name_free = NULL;
const char *name;
/* only stack-allocate (alloca) a few times. */
if (j++ < 5)
name = peers_psk_get_secret_name_a (peer->public_key, &name_free);
else {
name_free = peers_psk_get_secret_name_dup (peer->public_key);
name = name_free;
}
if (!func (setting, name, peer->preshared_key_flags, user_data))
continue;
}
{
nm_auto_unref_wgpeer NMWireGuardPeer *peer2 = NULL;
peer2 = nm_wireguard_peer_new_clone (peer, FALSE);
if (_peers_set (priv, peer2, i, FALSE))
peers_changed = TRUE;
}
}
if (peers_changed)
_peers_notify (setting);
return peers_changed;
}
return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->clear_secrets (sett_info,
property_idx,
setting,
func,
user_data);
}
static int
update_one_secret (NMSetting *setting,
const char *key,
GVariant *value,
GError **error)
{
NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
NMSettingWireGuardPrivate *priv;
gboolean has_changes = FALSE;
gboolean has_error = FALSE;
GVariantIter iter_peers;
GVariant *peer_var;
guint i_peer;
if (!nm_streq (key, NM_SETTING_WIREGUARD_PEERS)) {
return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->update_one_secret (setting,
key,
value,
error);
}
if (!g_variant_is_of_type (value, G_VARIANT_TYPE ("aa{sv}"))) {
g_set_error_literal (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
_("invalid peer secrets"));
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
return NM_SETTING_UPDATE_SECRET_ERROR;
}
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
g_variant_iter_init (&iter_peers, value);
i_peer = 0;
while (g_variant_iter_next (&iter_peers, "@a{sv}", &peer_var)) {
_nm_unused gs_unref_variant GVariant *peer_var_unref = peer_var;
nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
PeerData *pd;
const char *cstr;
i_peer++;
if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY, "&s", &cstr)) {
if (!has_error) {
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
_("peer #%u lacks public-key"),
i_peer - 1);
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
has_error = TRUE;
}
continue;
}
pd = _peers_get_by_public_key (priv, cstr, TRUE);
if (!pd) {
if (!has_error) {
g_set_error (error,
NM_CONNECTION_ERROR,
NM_CONNECTION_ERROR_PROPERTY_NOT_SECRET,
_("non-existing peer '%s'"),
cstr);
g_prefix_error (error, "%s.%s: ", NM_SETTING_WIREGUARD_SETTING_NAME, NM_SETTING_WIREGUARD_PEERS);
has_error = TRUE;
}
continue;
}
if (!g_variant_lookup (peer_var, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, "&s", &cstr)) {
/* no preshared-key. Ignore the rest.
*
* In particular, we don't reject all unknown fields. */
continue;
}
if (nm_streq0 (cstr, nm_wireguard_peer_get_preshared_key (pd->peer)))
continue;
peer = nm_wireguard_peer_new_clone (pd->peer, FALSE);
nm_wireguard_peer_set_preshared_key (peer, cstr, TRUE);
if (!_peers_set (priv, peer, pd->idx, FALSE))
nm_assert_not_reached ();
has_changes = TRUE;
}
if (has_error)
return NM_SETTING_UPDATE_SECRET_ERROR;
if (has_changes)
return NM_SETTING_UPDATE_SECRET_SUCCESS_MODIFIED;
return NM_SETTING_UPDATE_SECRET_SUCCESS_UNCHANGED;
}
static NMTernary
compare_property (const NMSettInfoSetting *sett_info,
guint property_idx,
NMConnection *con_a,
NMSetting *set_a,
NMConnection *con_b,
NMSetting *set_b,
NMSettingCompareFlags flags)
{
NMSettingWireGuardPrivate *a_priv;
NMSettingWireGuardPrivate *b_priv;
guint i;
if (nm_streq (sett_info->property_infos[property_idx].name, NM_SETTING_WIREGUARD_PEERS)) {
if (NM_FLAGS_HAS (flags, NM_SETTING_COMPARE_FLAG_INFERRABLE))
return NM_TERNARY_DEFAULT;
if (!set_b)
return TRUE;
a_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (set_a);
b_priv = NM_SETTING_WIREGUARD_GET_PRIVATE (set_b);
if (a_priv->peers_arr->len != b_priv->peers_arr->len)
return FALSE;
for (i = 0; i < a_priv->peers_arr->len; i++) {
NMWireGuardPeer *a_peer = _peers_get (a_priv, i)->peer;
NMWireGuardPeer *b_peer = _peers_get (b_priv, i)->peer;
if (nm_wireguard_peer_cmp (a_peer,
b_peer,
flags) != 0)
return FALSE;
}
return TRUE;
}
return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->compare_property (sett_info,
property_idx,
con_a,
set_a,
con_b,
set_b,
flags);
}
static void
duplicate_copy_properties (const NMSettInfoSetting *sett_info,
NMSetting *src,
NMSetting *dst)
{
NMSettingWireGuardPrivate *priv_src = NM_SETTING_WIREGUARD_GET_PRIVATE (src);
NMSettingWireGuardPrivate *priv_dst = NM_SETTING_WIREGUARD_GET_PRIVATE (dst);
guint i;
gboolean peers_changed = FALSE;
NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->duplicate_copy_properties (sett_info,
src,
dst);
/* We don't bother comparing the existing peers with what we are about to set.
* Always reset all. */
if (_peers_clear (priv_dst) > 0)
peers_changed = TRUE;
for (i = 0; i < priv_src->peers_arr->len; i++) {
if (_peers_append (priv_dst,
_peers_get (priv_src, i)->peer,
FALSE))
peers_changed = TRUE;
}
if (peers_changed)
_peers_notify (dst);
}
static void
enumerate_values (const NMSettInfoProperty *property_info,
NMSetting *setting,
NMSettingValueIterFn func,
gpointer user_data)
{
if (nm_streq (property_info->name, NM_SETTING_WIREGUARD_PEERS)) {
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
nm_auto_unset_gvalue GValue value = G_VALUE_INIT;
GPtrArray *ptr = NULL;
guint i;
if (priv->peers_arr && priv->peers_arr->len > 0) {
ptr = g_ptr_array_new_with_free_func ((GDestroyNotify) nm_wireguard_peer_unref);
for (i = 0; i < priv->peers_arr->len; i++)
g_ptr_array_add (ptr, nm_wireguard_peer_ref (_peers_get (priv, i)->peer));
}
g_value_init (&value, G_TYPE_PTR_ARRAY);
g_value_take_boxed (&value, ptr);
func (setting,
property_info->name,
&value,
0,
user_data);
return;
}
NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->enumerate_values (property_info,
setting,
func,
user_data);
}
static gboolean
aggregate (NMSetting *setting,
int type_i,
gpointer arg)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
NMConnectionAggregateType type = type_i;
NMSettingSecretFlags secret_flags;
guint i;
nm_assert (NM_IN_SET (type, NM_CONNECTION_AGGREGATE_ANY_SECRETS,
NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS));
switch (type) {
case NM_CONNECTION_AGGREGATE_ANY_SECRETS:
if (priv->private_key)
goto out_done;
for (i = 0; i < priv->peers_arr->len; i++) {
if (nm_wireguard_peer_get_preshared_key (_peers_get (priv, i)->peer))
goto out_done;
}
break;
case NM_CONNECTION_AGGREGATE_ANY_SYSTEM_SECRET_FLAGS:
#if NM_MORE_ASSERTS
if (!nm_setting_get_secret_flags (setting, NM_SETTING_WIREGUARD_PRIVATE_KEY, &secret_flags, NULL))
nm_assert_not_reached ();
nm_assert (secret_flags == priv->private_key_flags);
#endif
if (priv->private_key_flags == NM_SETTING_SECRET_FLAG_NONE)
goto out_done;
for (i = 0; i < priv->peers_arr->len; i++) {
secret_flags = nm_wireguard_peer_get_preshared_key_flags (_peers_get (priv, i)->peer);
if (secret_flags == NM_SETTING_SECRET_FLAG_NONE)
goto out_done;
}
break;
}
return FALSE;
out_done:
*((gboolean *) arg) = TRUE;
return TRUE;
}
static gboolean
get_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags *out_flags,
GError **error)
{
if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
gs_free char *public_key_free = NULL;
const char *public_key;
PeerData *pd;
public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
if ( public_key
&& (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
NM_SET_OUT (out_flags, nm_wireguard_peer_get_preshared_key_flags (pd->peer));
return TRUE;
}
}
return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->get_secret_flags (setting,
secret_name,
out_flags,
error);
}
static gboolean
set_secret_flags (NMSetting *setting,
const char *secret_name,
NMSettingSecretFlags flags,
GError **error)
{
if (NM_STR_HAS_PREFIX (secret_name, NM_SETTING_WIREGUARD_PEERS".")) {
NMSettingWireGuard *self = NM_SETTING_WIREGUARD (setting);
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (self);
gs_free char *public_key_free = NULL;
const char *public_key;
PeerData *pd;
public_key = peers_psk_get_secret_parse_a (secret_name, &public_key_free);
if ( public_key
&& (pd = _peers_get_by_public_key (priv, public_key, FALSE))) {
if (nm_wireguard_peer_get_preshared_key_flags (pd->peer) != flags) {
nm_auto_unref_wgpeer NMWireGuardPeer *peer = NULL;
peer = nm_wireguard_peer_new_clone (pd->peer, TRUE);
peer->preshared_key_flags = flags;
if (_peers_set (priv, peer, pd->idx, FALSE))
_peers_notify (self);
}
return TRUE;
}
}
return NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->set_secret_flags (setting,
secret_name,
flags,
error);
}
static void
for_each_secret (NMSetting *setting,
const char *data_key,
GVariant *data_val,
gboolean remove_non_secrets,
_NMConnectionForEachSecretFunc callback,
gpointer callback_data,
GVariantBuilder *setting_builder)
{
NMSettingWireGuard *s_wg;
NMSettingWireGuardPrivate *priv;
GVariantBuilder peers_builder;
GVariantIter *peer_iter;
GVariantIter data_iter;
const char *key;
if (!nm_streq (data_key, NM_SETTING_WIREGUARD_PEERS)) {
NM_SETTING_CLASS (nm_setting_wireguard_parent_class)->for_each_secret (setting,
data_key,
data_val,
remove_non_secrets,
callback,
callback_data,
setting_builder);
return;
}
if (!g_variant_is_of_type (data_val, G_VARIANT_TYPE ("aa{sv}"))) {
/* invalid type. Silently ignore content as we cannot find secret-keys
* here. */
return;
}
s_wg = NM_SETTING_WIREGUARD (setting);
priv = NM_SETTING_WIREGUARD_GET_PRIVATE (s_wg);
g_variant_builder_init (&peers_builder, G_VARIANT_TYPE ("aa{sv}"));
g_variant_iter_init (&data_iter, data_val);
while (g_variant_iter_next (&data_iter, "a{sv}", &peer_iter)) {
_nm_unused nm_auto_free_variant_iter GVariantIter *peer_iter_free = peer_iter;
gs_unref_variant GVariant *preshared_key = NULL;
PeerData *pd = NULL;
NMSettingSecretFlags secret_flags;
GVariant *val;
GVariantBuilder peer_builder;
g_variant_builder_init (&peer_builder, G_VARIANT_TYPE ("a{sv}"));
while (g_variant_iter_next (peer_iter, "{&sv}", &key, &val)) {
_nm_unused gs_unref_variant GVariant *val_free = val;
if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY)) {
if ( !preshared_key
&& g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
preshared_key = g_variant_ref (val);
continue;
}
if (nm_streq (key, NM_WIREGUARD_PEER_ATTR_PUBLIC_KEY)) {
if ( !pd
&& g_variant_is_of_type (val, G_VARIANT_TYPE_STRING))
pd = _peers_get_by_public_key (priv, g_variant_get_string (val, NULL), TRUE);
} else if (remove_non_secrets)
continue;
g_variant_builder_add (&peer_builder, "{sv}", key, val);
}
if (pd && preshared_key) {
/* without specifying a public-key of an existing peer, the secret is
* ignored. */
secret_flags = nm_wireguard_peer_get_preshared_key_flags (pd->peer);
if (callback (secret_flags, callback_data))
g_variant_builder_add (&peer_builder, "{sv}", NM_WIREGUARD_PEER_ATTR_PRESHARED_KEY, preshared_key);
}
g_variant_builder_add (&peers_builder, "a{sv}", &peer_builder);
}
g_variant_builder_add (setting_builder,
"{sv}",
NM_SETTING_WIREGUARD_PEERS,
g_variant_builder_end (&peers_builder));
}
/*****************************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMSettingWireGuard *setting = NM_SETTING_WIREGUARD (object);
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
switch (prop_id) {
case PROP_FWMARK:
g_value_set_uint (value, priv->fwmark);
break;
case PROP_IP4_AUTO_DEFAULT_ROUTE:
g_value_set_enum (value, priv->ip4_auto_default_route);
break;
case PROP_IP6_AUTO_DEFAULT_ROUTE:
g_value_set_enum (value, priv->ip6_auto_default_route);
break;
case PROP_LISTEN_PORT:
g_value_set_uint (value, priv->listen_port);
break;
case PROP_MTU:
g_value_set_uint (value, priv->mtu);
break;
case PROP_PEER_ROUTES:
g_value_set_boolean (value, priv->peer_routes);
break;
case PROP_PRIVATE_KEY:
g_value_set_string (value, priv->private_key);
break;
case PROP_PRIVATE_KEY_FLAGS:
g_value_set_flags (value, priv->private_key_flags);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
set_property (GObject *object, guint prop_id,
const GValue *value, GParamSpec *pspec)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (object);
const char *str;
switch (prop_id) {
case PROP_FWMARK:
priv->fwmark = g_value_get_uint (value);
break;
case PROP_IP4_AUTO_DEFAULT_ROUTE:
priv->ip4_auto_default_route = g_value_get_enum (value);
break;
case PROP_IP6_AUTO_DEFAULT_ROUTE:
priv->ip6_auto_default_route = g_value_get_enum (value);
break;
case PROP_LISTEN_PORT:
priv->listen_port = g_value_get_uint (value);
break;
case PROP_MTU:
priv->mtu = g_value_get_uint (value);
break;
case PROP_PEER_ROUTES:
priv->peer_routes = g_value_get_boolean (value);
break;
case PROP_PRIVATE_KEY:
nm_clear_pointer (&priv->private_key, nm_free_secret);
str = g_value_get_string (value);
if (str) {
if (nm_utils_base64secret_normalize (str,
NM_WIREGUARD_PUBLIC_KEY_LEN,
&priv->private_key))
priv->private_key_valid = TRUE;
else {
priv->private_key = g_strdup (str);
priv->private_key_valid = FALSE;
}
}
break;
case PROP_PRIVATE_KEY_FLAGS:
priv->private_key_flags = g_value_get_flags (value);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_setting_wireguard_init (NMSettingWireGuard *setting)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (setting);
priv->peers_arr = g_ptr_array_new ();
priv->peers_hash = g_hash_table_new (nm_pstr_hash, nm_pstr_equal);
priv->peer_routes = TRUE;
priv->ip4_auto_default_route = NM_TERNARY_DEFAULT;
priv->ip6_auto_default_route = NM_TERNARY_DEFAULT;
}
/**
* nm_setting_wireguard_new:
*
* Creates a new #NMSettingWireGuard object with default values.
*
* Returns: (transfer full): the new empty #NMSettingWireGuard object
*
* Since: 1.16
**/
NMSetting *
nm_setting_wireguard_new (void)
{
return g_object_new (NM_TYPE_SETTING_WIREGUARD, NULL);
}
static void
finalize (GObject *object)
{
NMSettingWireGuardPrivate *priv = NM_SETTING_WIREGUARD_GET_PRIVATE (object);
nm_free_secret (priv->private_key);
_peers_clear (priv);
g_ptr_array_unref (priv->peers_arr);
g_hash_table_unref (priv->peers_hash);
G_OBJECT_CLASS (nm_setting_wireguard_parent_class)->finalize (object);
}
static void
nm_setting_wireguard_class_init (NMSettingWireGuardClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NMSettingClass *setting_class = NM_SETTING_CLASS (klass);
GArray *properties_override = _nm_sett_info_property_override_create_array ();
object_class->get_property = get_property;
object_class->set_property = set_property;
object_class->finalize = finalize;
setting_class->verify = verify;
setting_class->verify_secrets = verify_secrets;
setting_class->need_secrets = need_secrets;
setting_class->clear_secrets = clear_secrets;
setting_class->update_one_secret = update_one_secret;
setting_class->compare_property = compare_property;
setting_class->duplicate_copy_properties = duplicate_copy_properties;
setting_class->enumerate_values = enumerate_values;
setting_class->aggregate = aggregate;
setting_class->get_secret_flags = get_secret_flags;
setting_class->set_secret_flags = set_secret_flags;
setting_class->for_each_secret = for_each_secret;
/**
* NMSettingWireGuard:private-key:
*
* The 256 bit private-key in base64 encoding.
*
* Since: 1.16
**/
obj_properties[PROP_PRIVATE_KEY] =
g_param_spec_string (NM_SETTING_WIREGUARD_PRIVATE_KEY, "", "",
NULL,
G_PARAM_READWRITE
| NM_SETTING_PARAM_SECRET
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:private-key-flags:
*
* Flags indicating how to handle the #NMSettingWirelessSecurity:private-key
* property.
*
* Since: 1.16
**/
obj_properties[PROP_PRIVATE_KEY_FLAGS] =
g_param_spec_flags (NM_SETTING_WIREGUARD_PRIVATE_KEY_FLAGS, "", "",
NM_TYPE_SETTING_SECRET_FLAGS,
NM_SETTING_SECRET_FLAG_NONE,
G_PARAM_READWRITE
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:fwmark:
*
* The use of fwmark is optional and is by default off. Setting it to 0
* disables it. Otherwise, it is a 32-bit fwmark for outgoing packets.
*
* Note that "ip4-auto-default-route" or "ip6-auto-default-route" enabled,
* implies to automatically choose a fwmark.
*
* Since: 1.16
**/
obj_properties[PROP_FWMARK] =
g_param_spec_uint (NM_SETTING_WIREGUARD_FWMARK, "", "",
0, G_MAXUINT32, 0,
G_PARAM_READWRITE
| NM_SETTING_PARAM_INFERRABLE
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:listen-port:
*
* The listen-port. If listen-port is not specified, the port will be chosen
* randomly when the interface comes up.
*
* Since: 1.16
**/
obj_properties[PROP_LISTEN_PORT] =
g_param_spec_uint (NM_SETTING_WIREGUARD_LISTEN_PORT, "", "",
0, 65535, 0,
G_PARAM_READWRITE
| NM_SETTING_PARAM_INFERRABLE
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:peer-routes:
*
* Whether to automatically add routes for the AllowedIPs ranges
* of the peers. If %TRUE (the default), NetworkManager will automatically
* add routes in the routing tables according to ipv4.route-table and
* ipv6.route-table. Usually you want this automatism enabled.
* If %FALSE, no such routes are added automatically. In this case, the
* user may want to configure static routes in ipv4.routes and ipv6.routes,
* respectively.
*
* Note that if the peer's AllowedIPs is "0.0.0.0/0" or "::/0" and the profile's
* ipv4.never-default or ipv6.never-default setting is enabled, the peer route for
* this peer won't be added automatically.
*
* Since: 1.16
**/
obj_properties[PROP_PEER_ROUTES] =
g_param_spec_boolean (NM_SETTING_WIREGUARD_PEER_ROUTES, "", "",
TRUE,
G_PARAM_READWRITE
| NM_SETTING_PARAM_INFERRABLE
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:mtu:
*
* If non-zero, only transmit packets of the specified size or smaller,
* breaking larger packets up into multiple fragments.
*
* If zero a default MTU is used. Note that contrary to wg-quick's MTU
* setting, this does not take into account the current routes at the
* time of activation.
*
* Since: 1.16
**/
obj_properties[PROP_MTU] =
g_param_spec_uint (NM_SETTING_WIREGUARD_MTU, "", "",
0, G_MAXUINT32, 0,
G_PARAM_READWRITE
| NM_SETTING_PARAM_INFERRABLE
| G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:ip4-auto-default-route:
*
* Whether to enable special handling of the IPv4 default route.
* If enabled, the IPv4 default route from wireguard.peer-routes
* will be placed to a dedicated routing-table and two policy routing rules
* will be added. The fwmark number is also used as routing-table for the default-route,
* and if fwmark is zero, an unused fwmark/table is chosen automatically.
* This corresponds to what wg-quick does with Table=auto and what WireGuard
* calls "Improved Rule-based Routing".
*
* Note that for this automatism to work, you usually don't want to set
* ipv4.gateway, because that will result in a conflicting default route.
*
* Leaving this at the default will enable this option automatically
* if ipv4.never-default is not set and there are any peers that use
* a default-route as allowed-ips.
*
* Since: 1.20
**/
obj_properties[PROP_IP4_AUTO_DEFAULT_ROUTE] =
g_param_spec_enum (NM_SETTING_WIREGUARD_IP4_AUTO_DEFAULT_ROUTE, "", "",
NM_TYPE_TERNARY,
NM_TERNARY_DEFAULT,
NM_SETTING_PARAM_FUZZY_IGNORE |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
/**
* NMSettingWireGuard:ip6-auto-default-route:
*
* Like ip4-auto-default-route, but for the IPv6 default route.
*
* Since: 1.20
**/
obj_properties[PROP_IP6_AUTO_DEFAULT_ROUTE] =
g_param_spec_enum (NM_SETTING_WIREGUARD_IP6_AUTO_DEFAULT_ROUTE, "", "",
NM_TYPE_TERNARY,
NM_TERNARY_DEFAULT,
NM_SETTING_PARAM_FUZZY_IGNORE |
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
/* ---dbus---
* property: peers
* format: array of 'a{sv}'
* description: Array of dictionaries for the WireGuard peers.
* ---end---
*/
_nm_properties_override_dbus (properties_override,
NM_SETTING_WIREGUARD_PEERS,
NM_SETT_INFO_PROPERT_TYPE (
.dbus_type = NM_G_VARIANT_TYPE ("aa{sv}"),
.to_dbus_fcn = _peers_dbus_only_synth,
.from_dbus_fcn = _peers_dbus_only_set,
));
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
_nm_setting_class_commit_full (setting_class, NM_META_SETTING_TYPE_WIREGUARD, NULL, properties_override);
}