// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2017 Red Hat, Inc.
*/
#include "nm-default.h"
#include "nm-device-macsec.h"
#include "nm-act-request.h"
#include "nm-device-private.h"
#include "platform/nm-platform.h"
#include "nm-device-factory.h"
#include "nm-manager.h"
#include "nm-setting-macsec.h"
#include "nm-core-internal.h"
#include "supplicant/nm-supplicant-manager.h"
#include "supplicant/nm-supplicant-interface.h"
#include "supplicant/nm-supplicant-config.h"
#include "nm-device-logging.h"
_LOG_DECLARE_SELF(NMDeviceMacsec);
/*****************************************************************************/
#define SUPPLICANT_LNK_TIMEOUT_SEC 15
/*****************************************************************************/
NM_GOBJECT_PROPERTIES_DEFINE (NMDeviceMacsec,
PROP_SCI,
PROP_CIPHER_SUITE,
PROP_ICV_LENGTH,
PROP_WINDOW,
PROP_ENCODING_SA,
PROP_ENCRYPT,
PROP_PROTECT,
PROP_INCLUDE_SCI,
PROP_ES,
PROP_SCB,
PROP_REPLAY_PROTECT,
PROP_VALIDATION,
);
typedef struct {
NMPlatformLnkMacsec props;
gulong parent_state_id;
gulong parent_mtu_id;
struct {
NMSupplicantManager *mgr;
NMSupplMgrCreateIfaceHandle *create_handle;
NMSupplicantInterface *iface;
gulong iface_state_id;
guint con_timeout_id;
guint lnk_timeout_id;
bool is_associated:1;
} supplicant;
NMActRequestGetSecretsCallId *macsec_secrets_id;
} NMDeviceMacsecPrivate;
struct _NMDeviceMacsec {
NMDevice parent;
NMDeviceMacsecPrivate _priv;
};
struct _NMDeviceMacsecClass {
NMDeviceClass parent;
};
G_DEFINE_TYPE (NMDeviceMacsec, nm_device_macsec, NM_TYPE_DEVICE)
#define NM_DEVICE_MACSEC_GET_PRIVATE(self) _NM_GET_PRIVATE (self, NMDeviceMacsec, NM_IS_DEVICE_MACSEC, NMDevice)
/******************************************************************/
static void macsec_secrets_cancel (NMDeviceMacsec *self);
/******************************************************************/
static
NM_UTILS_LOOKUP_STR_DEFINE (validation_mode_to_string, guint8,
NM_UTILS_LOOKUP_DEFAULT_WARN ("<unknown>"),
NM_UTILS_LOOKUP_STR_ITEM (0, "disable"),
NM_UTILS_LOOKUP_STR_ITEM (1, "check"),
NM_UTILS_LOOKUP_STR_ITEM (2, "strict"),
);
static void
parent_state_changed (NMDevice *parent,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason,
gpointer user_data)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
/* We'll react to our own carrier state notifications. Ignore the parent's. */
if (nm_device_state_reason_check (reason) == NM_DEVICE_STATE_REASON_CARRIER)
return;
nm_device_set_unmanaged_by_flags (NM_DEVICE (self), NM_UNMANAGED_PARENT, !nm_device_get_managed (parent, FALSE), reason);
}
static void
parent_mtu_maybe_changed (NMDevice *parent,
GParamSpec *pspec,
gpointer user_data)
{
/* the MTU of a MACsec device is limited by the parent's MTU.
*
* When the parent's MTU changes, try to re-set the MTU. */
nm_device_commit_mtu (user_data);
}
static void
parent_changed_notify (NMDevice *device,
int old_ifindex,
NMDevice *old_parent,
int new_ifindex,
NMDevice *new_parent)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (device);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NM_DEVICE_CLASS (nm_device_macsec_parent_class)->parent_changed_notify (device,
old_ifindex,
old_parent,
new_ifindex,
new_parent);
/* note that @self doesn't have to clear @parent_state_id on dispose,
* because NMDevice's dispose() will unset the parent, which in turn calls
* parent_changed_notify(). */
nm_clear_g_signal_handler (old_parent, &priv->parent_state_id);
nm_clear_g_signal_handler (old_parent, &priv->parent_mtu_id);
if (new_parent) {
priv->parent_state_id = g_signal_connect (new_parent,
NM_DEVICE_STATE_CHANGED,
G_CALLBACK (parent_state_changed),
device);
priv->parent_mtu_id = g_signal_connect (new_parent, "notify::" NM_DEVICE_MTU,
G_CALLBACK (parent_mtu_maybe_changed), device);
/* Set parent-dependent unmanaged flag */
nm_device_set_unmanaged_by_flags (device,
NM_UNMANAGED_PARENT,
!nm_device_get_managed (new_parent, FALSE),
NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED);
}
/* Recheck availability now that the parent has changed */
if (new_ifindex > 0) {
nm_device_queue_recheck_available (device,
NM_DEVICE_STATE_REASON_PARENT_CHANGED,
NM_DEVICE_STATE_REASON_PARENT_CHANGED);
}
}
static void
update_properties (NMDevice *device)
{
NMDeviceMacsec *self;
NMDeviceMacsecPrivate *priv;
const NMPlatformLink *plink = NULL;
const NMPlatformLnkMacsec *props = NULL;
int ifindex;
g_return_if_fail (NM_IS_DEVICE_MACSEC (device));
self = NM_DEVICE_MACSEC (device);
priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
ifindex = nm_device_get_ifindex (device);
g_return_if_fail (ifindex > 0);
props = nm_platform_link_get_lnk_macsec (nm_device_get_platform (device), ifindex, &plink);
if (!props) {
_LOGW (LOGD_PLATFORM, "could not get macsec properties");
return;
}
g_object_freeze_notify ((GObject *) device);
if (priv->props.parent_ifindex != props->parent_ifindex)
nm_device_parent_set_ifindex (device, props->parent_ifindex);
#define CHECK_PROPERTY_CHANGED(field, prop) \
G_STMT_START { \
if (priv->props.field != props->field) { \
priv->props.field = props->field; \
_notify (self, prop); \
} \
} G_STMT_END
CHECK_PROPERTY_CHANGED (sci, PROP_SCI);
CHECK_PROPERTY_CHANGED (cipher_suite, PROP_CIPHER_SUITE);
CHECK_PROPERTY_CHANGED (window, PROP_WINDOW);
CHECK_PROPERTY_CHANGED (icv_length, PROP_ICV_LENGTH);
CHECK_PROPERTY_CHANGED (encoding_sa, PROP_ENCODING_SA);
CHECK_PROPERTY_CHANGED (validation, PROP_VALIDATION);
CHECK_PROPERTY_CHANGED (encrypt, PROP_ENCRYPT);
CHECK_PROPERTY_CHANGED (protect, PROP_PROTECT);
CHECK_PROPERTY_CHANGED (include_sci, PROP_INCLUDE_SCI);
CHECK_PROPERTY_CHANGED (es, PROP_ES);
CHECK_PROPERTY_CHANGED (scb, PROP_SCB);
CHECK_PROPERTY_CHANGED (replay_protect, PROP_REPLAY_PROTECT);
g_object_thaw_notify ((GObject *) device);
}
static NMSupplicantConfig *
build_supplicant_config (NMDeviceMacsec *self, GError **error)
{
gs_unref_object NMSupplicantConfig *config = NULL;
NMSettingMacsec *s_macsec;
NMSetting8021x *s_8021x;
NMConnection *connection;
const char *con_uuid;
guint32 mtu;
connection = nm_device_get_applied_connection (NM_DEVICE (self));
g_return_val_if_fail (connection, NULL);
con_uuid = nm_connection_get_uuid (connection);
mtu = nm_platform_link_get_mtu (nm_device_get_platform (NM_DEVICE (self)),
nm_device_get_ifindex (NM_DEVICE (self)));
config = nm_supplicant_config_new (NM_SUPPL_CAP_MASK_NONE);
s_macsec = nm_device_get_applied_setting (NM_DEVICE (self), NM_TYPE_SETTING_MACSEC);
g_return_val_if_fail (s_macsec, NULL);
if (!nm_supplicant_config_add_setting_macsec (config, s_macsec, error)) {
g_prefix_error (error, "macsec-setting: ");
return NULL;
}
if (nm_setting_macsec_get_mode (s_macsec) == NM_SETTING_MACSEC_MODE_EAP) {
s_8021x = nm_connection_get_setting_802_1x (connection);
if (!nm_supplicant_config_add_setting_8021x (config, s_8021x, con_uuid, mtu, TRUE, error)) {
g_prefix_error (error, "802-1x-setting: ");
return NULL;
}
}
return g_steal_pointer (&config);
}
static void
supplicant_interface_release (NMDeviceMacsec *self)
{
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
nm_clear_pointer (&priv->supplicant.create_handle, nm_supplicant_manager_create_interface_cancel);
nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
nm_clear_g_source (&priv->supplicant.con_timeout_id);
nm_clear_g_signal_handler (priv->supplicant.iface, &priv->supplicant.iface_state_id);
if (priv->supplicant.iface) {
nm_supplicant_interface_disconnect (priv->supplicant.iface);
g_clear_object (&priv->supplicant.iface);
}
}
static void
macsec_secrets_cb (NMActRequest *req,
NMActRequestGetSecretsCallId *call_id,
NMSettingsConnection *connection,
GError *error,
gpointer user_data)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
NMDevice *device = NM_DEVICE (self);
NMDeviceMacsecPrivate *priv;
g_return_if_fail (NM_IS_DEVICE_MACSEC (self));
g_return_if_fail (NM_IS_ACT_REQUEST (req));
priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
g_return_if_fail (priv->macsec_secrets_id == call_id);
priv->macsec_secrets_id = NULL;
if (g_error_matches (error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
g_return_if_fail (req == nm_device_get_act_request (device));
g_return_if_fail (nm_device_get_state (device) == NM_DEVICE_STATE_NEED_AUTH);
g_return_if_fail (nm_act_request_get_settings_connection (req) == connection);
if (error) {
_LOGW (LOGD_ETHER, "%s", error->message);
nm_device_state_changed (device,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_NO_SECRETS);
return;
}
nm_device_activate_schedule_stage1_device_prepare (device, FALSE);
}
static void
macsec_secrets_cancel (NMDeviceMacsec *self)
{
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
if (priv->macsec_secrets_id)
nm_act_request_cancel_secrets (NULL, priv->macsec_secrets_id);
nm_assert (!priv->macsec_secrets_id);
}
static void
macsec_secrets_get_secrets (NMDeviceMacsec *self,
const char *setting_name,
NMSecretAgentGetSecretsFlags flags)
{
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NMActRequest *req;
macsec_secrets_cancel (self);
req = nm_device_get_act_request (NM_DEVICE (self));
g_return_if_fail (NM_IS_ACT_REQUEST (req));
priv->macsec_secrets_id = nm_act_request_get_secrets (req,
TRUE,
setting_name,
flags,
NULL,
macsec_secrets_cb,
self);
g_return_if_fail (priv->macsec_secrets_id);
}
static gboolean
supplicant_lnk_timeout_cb (gpointer user_data)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NMDevice *dev = NM_DEVICE (self);
NMActRequest *req;
NMConnection *applied_connection;
const char *setting_name;
priv->supplicant.lnk_timeout_id = 0;
req = nm_device_get_act_request (dev);
if (nm_device_get_state (dev) == NM_DEVICE_STATE_ACTIVATED) {
nm_device_state_changed (dev,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT);
return G_SOURCE_REMOVE;
}
/* Disconnect event during initial authentication and credentials
* ARE checked - we are likely to have wrong key. Ask the user for
* another one.
*/
if (nm_device_get_state (dev) != NM_DEVICE_STATE_CONFIG)
goto time_out;
nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
applied_connection = nm_act_request_get_applied_connection (req);
setting_name = nm_connection_need_secrets (applied_connection, NULL);
if (!setting_name)
goto time_out;
_LOGI (LOGD_DEVICE | LOGD_ETHER,
"Activation: disconnected during authentication, asking for new key.");
supplicant_interface_release (self);
nm_device_state_changed (dev, NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
macsec_secrets_get_secrets (self, setting_name, NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW);
return G_SOURCE_REMOVE;
time_out:
_LOGW (LOGD_DEVICE | LOGD_ETHER, "link timed out.");
nm_device_state_changed (dev, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT);
return G_SOURCE_REMOVE;
}
static void
supplicant_iface_state_is_completed (NMDeviceMacsec *self,
NMSupplicantInterfaceState state)
{
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
if (state == NM_SUPPLICANT_INTERFACE_STATE_COMPLETED) {
nm_clear_g_source (&priv->supplicant.lnk_timeout_id);
nm_clear_g_source (&priv->supplicant.con_timeout_id);
nm_device_bring_up (NM_DEVICE (self), TRUE, NULL);
/* If this is the initial association during device activation,
* schedule the next activation stage.
*/
if (nm_device_get_state (NM_DEVICE (self)) == NM_DEVICE_STATE_CONFIG) {
_LOGI (LOGD_DEVICE,
"Activation: Stage 2 of 5 (Device Configure) successful.");
nm_device_activate_schedule_stage3_ip_config_start (NM_DEVICE (self));
}
return;
}
if ( !priv->supplicant.lnk_timeout_id
&& !priv->supplicant.con_timeout_id)
priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
}
static void
supplicant_iface_assoc_cb (NMSupplicantInterface *iface,
GError *error,
gpointer user_data)
{
NMDeviceMacsec *self;
NMDeviceMacsecPrivate *priv;
if (nm_utils_error_is_cancelled_or_disposing (error))
return;
self = user_data;
priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
if (error) {
supplicant_interface_release (self);
nm_device_queue_state (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
return;
}
nm_assert (!priv->supplicant.lnk_timeout_id);
nm_assert (!priv->supplicant.is_associated);
priv->supplicant.is_associated = TRUE;
supplicant_iface_state_is_completed (self,
nm_supplicant_interface_get_state (priv->supplicant.iface));
}
static gboolean
supplicant_iface_start (NMDeviceMacsec *self)
{
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
gs_unref_object NMSupplicantConfig *config = NULL;
gs_free_error GError *error = NULL;
config = build_supplicant_config (self, &error);
if (!config) {
_LOGE (LOGD_DEVICE,
"Activation: couldn't build security configuration: %s",
error->message);
supplicant_interface_release (self);
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED);
return FALSE;
}
nm_supplicant_interface_disconnect (priv->supplicant.iface);
nm_supplicant_interface_assoc (priv->supplicant.iface,
config,
supplicant_iface_assoc_cb,
self);
return TRUE;
}
static void
supplicant_iface_state_cb (NMSupplicantInterface *iface,
int new_state_i,
int old_state_i,
int disconnect_reason,
gpointer user_data)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NMSupplicantInterfaceState new_state = new_state_i;
NMSupplicantInterfaceState old_state = old_state_i;
_LOGI (LOGD_DEVICE, "supplicant interface state: %s -> %s",
nm_supplicant_interface_state_to_string (old_state),
nm_supplicant_interface_state_to_string (new_state));
if (new_state == NM_SUPPLICANT_INTERFACE_STATE_DOWN) {
supplicant_interface_release (self);
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
return;
}
if (old_state == NM_SUPPLICANT_INTERFACE_STATE_STARTING) {
if (!supplicant_iface_start (self))
return;
}
if (priv->supplicant.is_associated)
supplicant_iface_state_is_completed (self, new_state);
}
static gboolean
handle_auth_or_fail (NMDeviceMacsec *self,
NMActRequest *req,
gboolean new_secrets)
{
const char *setting_name;
NMConnection *applied_connection;
if (!nm_device_auth_retries_try_next (NM_DEVICE (self)))
return FALSE;
nm_device_state_changed (NM_DEVICE (self), NM_DEVICE_STATE_NEED_AUTH, NM_DEVICE_STATE_REASON_NONE);
nm_active_connection_clear_secrets (NM_ACTIVE_CONNECTION (req));
applied_connection = nm_act_request_get_applied_connection (req);
setting_name = nm_connection_need_secrets (applied_connection, NULL);
if (!setting_name) {
_LOGI (LOGD_DEVICE, "Cleared secrets, but setting didn't need any secrets.");
return FALSE;
}
macsec_secrets_get_secrets (self, setting_name,
NM_SECRET_AGENT_GET_SECRETS_FLAG_ALLOW_INTERACTION
| (new_secrets ? NM_SECRET_AGENT_GET_SECRETS_FLAG_REQUEST_NEW : 0));
return TRUE;
}
static gboolean
supplicant_connection_timeout_cb (gpointer user_data)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (user_data);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NMDevice *device = NM_DEVICE (self);
NMActRequest *req;
NMSettingsConnection *connection;
guint64 timestamp = 0;
gboolean new_secrets = TRUE;
priv->supplicant.con_timeout_id = 0;
/* Authentication failed; either driver problems, the encryption key is
* wrong, the passwords or certificates were wrong or the Ethernet switch's
* port is not configured for 802.1x. */
_LOGW (LOGD_DEVICE,
"Activation: (macsec) association took too long.");
supplicant_interface_release (self);
req = nm_device_get_act_request (device);
connection = nm_act_request_get_settings_connection (req);
g_return_val_if_fail (connection, G_SOURCE_REMOVE);
/* Ask for new secrets only if we've never activated this connection
* before. If we've connected before, don't bother the user with dialogs,
* just retry or fail, and if we never connect the user can fix the
* password somewhere else. */
if (nm_settings_connection_get_timestamp (connection, ×tamp))
new_secrets = !timestamp;
if (!handle_auth_or_fail (self, req, new_secrets)) {
nm_device_state_changed (device, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_NO_SECRETS);
return G_SOURCE_REMOVE;
}
_LOGW (LOGD_DEVICE, "Activation: (macsec) asking for new secrets");
if ( !priv->supplicant.lnk_timeout_id
&& priv->supplicant.iface) {
NMSupplicantInterfaceState state;
state = nm_supplicant_interface_get_state (priv->supplicant.iface);
if (state != NM_SUPPLICANT_INTERFACE_STATE_COMPLETED
&& nm_supplicant_interface_state_is_operational (state))
priv->supplicant.lnk_timeout_id = g_timeout_add_seconds (SUPPLICANT_LNK_TIMEOUT_SEC, supplicant_lnk_timeout_cb, self);
}
return G_SOURCE_REMOVE;
}
static void
supplicant_interface_create_cb (NMSupplicantManager *supplicant_manager,
NMSupplMgrCreateIfaceHandle *handle,
NMSupplicantInterface *iface,
GError *error,
gpointer user_data)
{
NMDeviceMacsec *self;
NMDeviceMacsecPrivate *priv;
guint timeout;
if (nm_utils_error_is_cancelled (error))
return;
self = user_data;
priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
nm_assert (priv->supplicant.create_handle == handle);
priv->supplicant.create_handle = NULL;
if (error) {
_LOGE (LOGD_DEVICE,
"Couldn't initialize supplicant interface: %s",
error->message);
supplicant_interface_release (self);
nm_device_state_changed (NM_DEVICE (self),
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED);
return;
}
priv->supplicant.iface = g_object_ref (iface);
priv->supplicant.is_associated = FALSE;
priv->supplicant.iface_state_id = g_signal_connect (priv->supplicant.iface,
NM_SUPPLICANT_INTERFACE_STATE,
G_CALLBACK (supplicant_iface_state_cb),
self);
timeout = nm_device_get_supplicant_timeout (NM_DEVICE (self));
priv->supplicant.con_timeout_id = g_timeout_add_seconds (timeout,
supplicant_connection_timeout_cb,
self);
if (nm_supplicant_interface_state_is_operational (nm_supplicant_interface_get_state (iface)))
supplicant_iface_start (self);
}
static NMActStageReturn
act_stage2_config (NMDevice *device, NMDeviceStateReason *out_failure_reason)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (device);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
NMConnection *connection;
NMDevice *parent;
const char *setting_name;
int ifindex;
connection = nm_device_get_applied_connection (NM_DEVICE (self));
g_return_val_if_fail (connection, NM_ACT_STAGE_RETURN_FAILURE);
if (!priv->supplicant.mgr)
priv->supplicant.mgr = g_object_ref (nm_supplicant_manager_get ());
/* If we need secrets, get them */
setting_name = nm_connection_need_secrets (connection, NULL);
if (setting_name) {
NMActRequest *req = nm_device_get_act_request (NM_DEVICE (self));
_LOGI (LOGD_DEVICE,
"Activation: connection '%s' has security, but secrets are required.",
nm_connection_get_id (connection));
if (!handle_auth_or_fail (self, req, FALSE)) {
NM_SET_OUT (out_failure_reason, NM_DEVICE_STATE_REASON_NO_SECRETS);
return NM_ACT_STAGE_RETURN_FAILURE;
}
return NM_ACT_STAGE_RETURN_POSTPONE;
}
_LOGI (LOGD_DEVICE | LOGD_ETHER,
"Activation: connection '%s' requires no security. No secrets needed.",
nm_connection_get_id (connection));
supplicant_interface_release (self);
parent = nm_device_parent_get_device (NM_DEVICE (self));
g_return_val_if_fail (parent, NM_ACT_STAGE_RETURN_FAILURE);
ifindex = nm_device_get_ifindex (parent);
g_return_val_if_fail (ifindex > 0, NM_ACT_STAGE_RETURN_FAILURE);
priv->supplicant.create_handle = nm_supplicant_manager_create_interface (priv->supplicant.mgr,
ifindex,
NM_SUPPLICANT_DRIVER_MACSEC,
supplicant_interface_create_cb,
self);
return NM_ACT_STAGE_RETURN_POSTPONE;
}
static void
deactivate (NMDevice *device)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (device);
supplicant_interface_release (self);
}
/******************************************************************/
static NMDeviceCapabilities
get_generic_capabilities (NMDevice *dev)
{
/* We assume MACsec interfaces always support carrier detect */
return NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_IS_SOFTWARE;
}
/******************************************************************/
static gboolean
is_available (NMDevice *device, NMDeviceCheckDevAvailableFlags flags)
{
if (!nm_device_parent_get_device (device))
return FALSE;
return NM_DEVICE_CLASS (nm_device_macsec_parent_class)->is_available (device, flags);
}
static gboolean
create_and_realize (NMDevice *device,
NMConnection *connection,
NMDevice *parent,
const NMPlatformLink **out_plink,
GError **error)
{
const char *iface = nm_device_get_iface (device);
NMSettingMacsec *s_macsec;
NMPlatformLnkMacsec lnk = { };
int parent_ifindex;
const char *hw_addr;
union {
struct {
guint8 mac[6];
guint16 port;
} s;
guint64 u;
} sci;
int r;
s_macsec = nm_connection_get_setting_macsec (connection);
g_assert (s_macsec);
if (!parent) {
g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_MISSING_DEPENDENCIES,
"MACsec devices can not be created without a parent interface");
return FALSE;
}
lnk.encrypt = nm_setting_macsec_get_encrypt (s_macsec);
hw_addr = nm_device_get_hw_address (parent);
if (!hw_addr) {
g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED,
"can't read parent MAC");
return FALSE;
}
nm_utils_hwaddr_aton (hw_addr, sci.s.mac, ETH_ALEN);
sci.s.port = htons (nm_setting_macsec_get_port (s_macsec));
lnk.sci = be64toh (sci.u);
lnk.validation = nm_setting_macsec_get_validation (s_macsec);
lnk.include_sci = nm_setting_macsec_get_send_sci (s_macsec);
parent_ifindex = nm_device_get_ifindex (parent);
g_warn_if_fail (parent_ifindex > 0);
r = nm_platform_link_macsec_add (nm_device_get_platform (device), iface, parent_ifindex, &lnk, out_plink);
if (r < 0) {
g_set_error (error, NM_DEVICE_ERROR, NM_DEVICE_ERROR_CREATION_FAILED,
"Failed to create macsec interface '%s' for '%s': %s",
iface,
nm_connection_get_id (connection),
nm_strerror (r));
return FALSE;
}
nm_device_parent_set_ifindex (device, parent_ifindex);
return TRUE;
}
static void
link_changed (NMDevice *device,
const NMPlatformLink *pllink)
{
NM_DEVICE_CLASS (nm_device_macsec_parent_class)->link_changed (device, pllink);
update_properties (device);
}
static void
device_state_changed (NMDevice *device,
NMDeviceState new_state,
NMDeviceState old_state,
NMDeviceStateReason reason)
{
if (new_state > NM_DEVICE_STATE_ACTIVATED)
macsec_secrets_cancel (NM_DEVICE_MACSEC (device));
}
/******************************************************************/
static void
get_property (GObject *object, guint prop_id,
GValue *value, GParamSpec *pspec)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (object);
NMDeviceMacsecPrivate *priv = NM_DEVICE_MACSEC_GET_PRIVATE (self);
switch (prop_id) {
case PROP_SCI:
g_value_set_uint64 (value, priv->props.sci);
break;
case PROP_CIPHER_SUITE:
g_value_set_uint64 (value, priv->props.cipher_suite);
break;
case PROP_ICV_LENGTH:
g_value_set_uchar (value, priv->props.icv_length);
break;
case PROP_WINDOW:
g_value_set_uint (value, priv->props.window);
break;
case PROP_ENCODING_SA:
g_value_set_uchar (value, priv->props.encoding_sa);
break;
case PROP_ENCRYPT:
g_value_set_boolean (value, priv->props.encrypt);
break;
case PROP_PROTECT:
g_value_set_boolean (value, priv->props.protect);
break;
case PROP_INCLUDE_SCI:
g_value_set_boolean (value, priv->props.include_sci);
break;
case PROP_ES:
g_value_set_boolean (value, priv->props.es);
break;
case PROP_SCB:
g_value_set_boolean (value, priv->props.scb);
break;
case PROP_REPLAY_PROTECT:
g_value_set_boolean (value, priv->props.replay_protect);
break;
case PROP_VALIDATION:
g_value_set_string (value,
validation_mode_to_string (priv->props.validation));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
nm_device_macsec_init (NMDeviceMacsec *self)
{
}
static void
dispose (GObject *object)
{
NMDeviceMacsec *self = NM_DEVICE_MACSEC (object);
macsec_secrets_cancel (self);
supplicant_interface_release (self);
G_OBJECT_CLASS (nm_device_macsec_parent_class)->dispose (object);
nm_assert (NM_DEVICE_MACSEC_GET_PRIVATE (self)->parent_state_id == 0);
nm_assert (NM_DEVICE_MACSEC_GET_PRIVATE (self)->parent_mtu_id == 0);
}
static const NMDBusInterfaceInfoExtended interface_info_device_macsec = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT (
NM_DBUS_INTERFACE_DEVICE_MACSEC,
.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS (
&nm_signal_info_property_changed_legacy,
),
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS (
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Parent", "o", NM_DEVICE_PARENT),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Sci", "t", NM_DEVICE_MACSEC_SCI),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("IcvLength", "y", NM_DEVICE_MACSEC_ICV_LENGTH),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("CipherSuite", "t", NM_DEVICE_MACSEC_CIPHER_SUITE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Window", "u", NM_DEVICE_MACSEC_WINDOW),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("EncodingSa", "y", NM_DEVICE_MACSEC_ENCODING_SA),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Validation", "s", NM_DEVICE_MACSEC_VALIDATION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Encrypt", "b", NM_DEVICE_MACSEC_ENCRYPT),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Protect", "b", NM_DEVICE_MACSEC_PROTECT),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("IncludeSci", "b", NM_DEVICE_MACSEC_INCLUDE_SCI),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Es", "b", NM_DEVICE_MACSEC_ES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("Scb", "b", NM_DEVICE_MACSEC_SCB),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L ("ReplayProtect", "b", NM_DEVICE_MACSEC_REPLAY_PROTECT),
),
),
.legacy_property_changed = TRUE,
};
static void
nm_device_macsec_class_init (NMDeviceMacsecClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS (klass);
NMDeviceClass *device_class = NM_DEVICE_CLASS (klass);
object_class->get_property = get_property;
object_class->dispose = dispose;
dbus_object_class->interface_infos = NM_DBUS_INTERFACE_INFOS (&interface_info_device_macsec);
device_class->connection_type_supported = NM_SETTING_MACSEC_SETTING_NAME;
device_class->connection_type_check_compatible = NM_SETTING_MACSEC_SETTING_NAME;
device_class->link_types = NM_DEVICE_DEFINE_LINK_TYPES (NM_LINK_TYPE_MACSEC);
device_class->mtu_parent_delta = 32;
device_class->act_stage2_config = act_stage2_config;
device_class->create_and_realize = create_and_realize;
device_class->deactivate = deactivate;
device_class->get_generic_capabilities = get_generic_capabilities;
device_class->link_changed = link_changed;
device_class->is_available = is_available;
device_class->parent_changed_notify = parent_changed_notify;
device_class->state_changed = device_state_changed;
device_class->get_configured_mtu = nm_device_get_configured_mtu_wired_parent;
obj_properties[PROP_SCI] =
g_param_spec_uint64 (NM_DEVICE_MACSEC_SCI, "", "",
0, G_MAXUINT64, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CIPHER_SUITE] =
g_param_spec_uint64 (NM_DEVICE_MACSEC_CIPHER_SUITE, "", "",
0, G_MAXUINT64, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ICV_LENGTH] =
g_param_spec_uchar (NM_DEVICE_MACSEC_ICV_LENGTH, "", "",
0, G_MAXUINT8, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_WINDOW] =
g_param_spec_uint (NM_DEVICE_MACSEC_WINDOW, "", "",
0, G_MAXUINT32, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ENCODING_SA] =
g_param_spec_uchar (NM_DEVICE_MACSEC_ENCODING_SA, "", "",
0, 3, 0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_VALIDATION] =
g_param_spec_string (NM_DEVICE_MACSEC_VALIDATION, "", "",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ENCRYPT] =
g_param_spec_boolean (NM_DEVICE_MACSEC_ENCRYPT, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PROTECT] =
g_param_spec_boolean (NM_DEVICE_MACSEC_PROTECT, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_INCLUDE_SCI] =
g_param_spec_boolean (NM_DEVICE_MACSEC_INCLUDE_SCI, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ES] =
g_param_spec_boolean (NM_DEVICE_MACSEC_ES, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_SCB] =
g_param_spec_boolean (NM_DEVICE_MACSEC_SCB, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_REPLAY_PROTECT] =
g_param_spec_boolean (NM_DEVICE_MACSEC_REPLAY_PROTECT, "", "",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, _PROPERTY_ENUMS_LAST, obj_properties);
}
/*************************************************************/
#define NM_TYPE_MACSEC_DEVICE_FACTORY (nm_macsec_device_factory_get_type ())
#define NM_MACSEC_DEVICE_FACTORY(obj) (G_TYPE_CHECK_INSTANCE_CAST ((obj), NM_TYPE_MACSEC_DEVICE_FACTORY, NMMacsecDeviceFactory))
static NMDevice *
create_device (NMDeviceFactory *factory,
const char *iface,
const NMPlatformLink *plink,
NMConnection *connection,
gboolean *out_ignore)
{
return (NMDevice *) g_object_new (NM_TYPE_DEVICE_MACSEC,
NM_DEVICE_IFACE, iface,
NM_DEVICE_TYPE_DESC, "Macsec",
NM_DEVICE_DEVICE_TYPE, NM_DEVICE_TYPE_MACSEC,
NM_DEVICE_LINK_TYPE, NM_LINK_TYPE_MACSEC,
NULL);
}
static const char *
get_connection_parent (NMDeviceFactory *factory, NMConnection *connection)
{
NMSettingMacsec *s_macsec;
NMSettingWired *s_wired;
const char *parent = NULL;
g_return_val_if_fail (nm_connection_is_type (connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
s_macsec = nm_connection_get_setting_macsec (connection);
g_assert (s_macsec);
parent = nm_setting_macsec_get_parent (s_macsec);
if (parent)
return parent;
/* Try the hardware address from the MACsec connection's hardware setting */
s_wired = nm_connection_get_setting_wired (connection);
if (s_wired)
return nm_setting_wired_get_mac_address (s_wired);
return NULL;
}
static char *
get_connection_iface (NMDeviceFactory *factory,
NMConnection *connection,
const char *parent_iface)
{
NMSettingMacsec *s_macsec;
const char *ifname;
g_return_val_if_fail (nm_connection_is_type (connection, NM_SETTING_MACSEC_SETTING_NAME), NULL);
s_macsec = nm_connection_get_setting_macsec (connection);
g_assert (s_macsec);
if (!parent_iface)
return NULL;
ifname = nm_connection_get_interface_name (connection);
return g_strdup (ifname);
}
NM_DEVICE_FACTORY_DEFINE_INTERNAL (MACSEC, Macsec, macsec,
NM_DEVICE_FACTORY_DECLARE_LINK_TYPES (NM_LINK_TYPE_MACSEC)
NM_DEVICE_FACTORY_DECLARE_SETTING_TYPES (NM_SETTING_MACSEC_SETTING_NAME),
factory_class->create_device = create_device;
factory_class->get_connection_parent = get_connection_parent;
factory_class->get_connection_iface = get_connection_iface;
)