/* SPDX-License-Identifier: LGPL-2.1-or-later */
/*
* Copyright (C) 2019 Red Hat, Inc.
*/
#include "nm-glib-aux/nm-default-glib-i18n-lib.h"
#include "nm-libnm-core-aux.h"
#include "nm-libnm-core-intern/nm-libnm-core-utils.h"
/*****************************************************************************/
typedef enum {
KEY_TYPE_STRING,
KEY_TYPE_INT,
KEY_TYPE_BOOL,
} KeyType;
typedef struct {
const char *str_val;
union {
int vint;
bool vbool;
} typ_val;
} ParseData;
typedef struct {
const char * name;
NMTeamLinkWatcherType watcher_type;
KeyType key_type;
union {
int (*fint)(const NMTeamLinkWatcher *watcher);
gboolean (*fbool)(const NMTeamLinkWatcher *watcher);
const char *(*fstring)(const NMTeamLinkWatcher *watcher);
} get_fcn;
union {
int vint;
bool vbool;
} def_val;
} TeamLinkWatcherKeyInfo;
static gboolean
_team_link_watcher_validate_active(const NMTeamLinkWatcher *watcher)
{
return NM_FLAGS_HAS(nm_team_link_watcher_get_flags(watcher),
NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE);
}
static gboolean
_team_link_watcher_validate_inactive(const NMTeamLinkWatcher *watcher)
{
return NM_FLAGS_HAS(nm_team_link_watcher_get_flags(watcher),
NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE);
}
static gboolean
_team_link_watcher_send_always(const NMTeamLinkWatcher *watcher)
{
return NM_FLAGS_HAS(nm_team_link_watcher_get_flags(watcher),
NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS);
}
static const TeamLinkWatcherKeyInfo _team_link_watcher_key_infos[_NM_TEAM_LINK_WATCHER_KEY_NUM] = {
#define _KEY_INFO(key_id, _name, _watcher_type, _key_type, ...) \
[key_id] = {.name = ""_name \
"", \
.watcher_type = (_watcher_type), \
.key_type = _key_type, \
##__VA_ARGS__}
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_NAME,
"name",
NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL | NM_TEAM_LINK_WATCHER_TYPE_NSNAPING
| NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_STRING,
.get_fcn.fstring = nm_team_link_watcher_get_name, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_DELAY_UP,
"delay-up",
NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_delay_up, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN,
"delay-down",
NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_delay_down, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT,
"init-wait",
NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_init_wait, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_INTERVAL,
"interval",
NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_interval, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX,
"missed-max",
NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_missed_max,
.def_val.vint = 3, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST,
"target-host",
NM_TEAM_LINK_WATCHER_TYPE_NSNAPING | NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_STRING,
.get_fcn.fstring = nm_team_link_watcher_get_target_host, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_VLANID,
"vlanid",
NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_INT,
.get_fcn.fint = nm_team_link_watcher_get_vlanid,
.def_val.vint = -1, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST,
"source-host",
NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_STRING,
.get_fcn.fstring = nm_team_link_watcher_get_source_host, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE,
"validate-active",
NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_BOOL,
.get_fcn.fbool = _team_link_watcher_validate_active, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE,
"validate-inactive",
NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_BOOL,
.get_fcn.fbool = _team_link_watcher_validate_inactive, ),
_KEY_INFO(NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS,
"send-always",
NM_TEAM_LINK_WATCHER_TYPE_ARPING,
KEY_TYPE_BOOL,
.get_fcn.fbool = _team_link_watcher_send_always, ),
};
static NMTeamLinkWatcherType
_team_link_watcher_get_watcher_type_from_name(const char *name)
{
if (name) {
if (nm_streq(name, NM_TEAM_LINK_WATCHER_ETHTOOL))
return NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL;
if (nm_streq(name, NM_TEAM_LINK_WATCHER_NSNA_PING))
return NM_TEAM_LINK_WATCHER_TYPE_NSNAPING;
if (nm_streq(name, NM_TEAM_LINK_WATCHER_ARP_PING))
return NM_TEAM_LINK_WATCHER_TYPE_ARPING;
}
return NM_TEAM_LINK_WATCHER_TYPE_NONE;
}
static const char *
_parse_data_get_str(const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
NMTeamLinkWatcherKeyId key_id)
{
nm_assert(_NM_INT_NOT_NEGATIVE(key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
nm_assert(_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_STRING);
return parse_data[key_id].str_val;
}
static int
_parse_data_get_int(const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
NMTeamLinkWatcherKeyId key_id)
{
nm_assert(_NM_INT_NOT_NEGATIVE(key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
nm_assert(_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_INT);
if (parse_data[key_id].str_val)
return parse_data[key_id].typ_val.vint;
return _team_link_watcher_key_infos[key_id].def_val.vint;
}
static int
_parse_data_get_bool(const ParseData parse_data[static _NM_TEAM_LINK_WATCHER_KEY_NUM],
NMTeamLinkWatcherKeyId key_id)
{
nm_assert(_NM_INT_NOT_NEGATIVE(key_id) && key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM);
nm_assert(_team_link_watcher_key_infos[key_id].key_type == KEY_TYPE_BOOL);
if (parse_data[key_id].str_val)
return parse_data[key_id].typ_val.vbool;
return _team_link_watcher_key_infos[key_id].def_val.vbool;
}
char *
nm_utils_team_link_watcher_to_string(const NMTeamLinkWatcher *watcher)
{
nm_auto_free_gstring GString *str = NULL;
const char * name;
NMTeamLinkWatcherType watcher_type;
NMTeamLinkWatcherKeyId key_id;
if (!watcher)
return NULL;
str = g_string_new(NULL);
name = nm_team_link_watcher_get_name(watcher);
g_string_append_printf(str, "name=%s", name ?: "");
watcher_type = _team_link_watcher_get_watcher_type_from_name(name);
for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id];
const char * vstr;
int vint;
bool vbool;
nm_assert(
info->name && info->name
&& NM_STRCHAR_ALL(info->name, ch, ((ch >= 'a' && ch <= 'z') || NM_IN_SET(ch, '-'))));
nm_assert(NM_IN_SET(info->key_type, KEY_TYPE_STRING, KEY_TYPE_INT, KEY_TYPE_BOOL));
if (key_id == NM_TEAM_LINK_WATCHER_KEY_NAME)
continue;
if (!NM_FLAGS_ALL(info->watcher_type, watcher_type))
continue;
switch (info->key_type) {
case KEY_TYPE_STRING:
vstr = info->get_fcn.fstring(watcher);
if (vstr) {
g_string_append_printf(nm_gstring_add_space_delimiter(str),
"%s=%s",
info->name,
vstr);
}
break;
case KEY_TYPE_INT:
vint = info->get_fcn.fint(watcher);
if (vint != info->def_val.vint) {
g_string_append_printf(nm_gstring_add_space_delimiter(str),
"%s=%d",
info->name,
vint);
}
break;
case KEY_TYPE_BOOL:
vbool = info->get_fcn.fbool(watcher);
if (vbool != info->def_val.vbool) {
g_string_append_printf(nm_gstring_add_space_delimiter(str),
"%s=%s",
info->name,
vbool ? "true" : "false");
}
break;
}
}
return g_string_free(g_steal_pointer(&str), FALSE);
}
NMTeamLinkWatcher *
nm_utils_team_link_watcher_from_string(const char *str, GError **error)
{
gs_free const char ** tokens = NULL;
ParseData parse_data[_NM_TEAM_LINK_WATCHER_KEY_NUM] = {};
NMTeamLinkWatcherType watcher_type;
NMTeamLinkWatcherKeyId key_id;
gsize i_token;
NMTeamLinkWatcher * watcher;
int errsv;
g_return_val_if_fail(str, NULL);
g_return_val_if_fail(!error || !*error, NULL);
tokens = nm_utils_escaped_tokens_split(str, NM_ASCII_SPACES);
if (!tokens) {
g_set_error(error, 1, 0, "'%s' is not valid", str);
return NULL;
}
for (i_token = 0; tokens[i_token]; i_token++) {
const TeamLinkWatcherKeyInfo *info;
const char * key = tokens[i_token];
const char * val;
val = strchr(key, '=');
if (!val) {
nm_utils_error_set(
error,
NM_UTILS_ERROR_UNKNOWN,
_("'%s' is not valid: properties should be specified as 'key=value'"),
key);
return NULL;
}
((char *) val)[0] = '\0';
val++;
for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
info = &_team_link_watcher_key_infos[key_id];
if (nm_streq(key, info->name))
break;
}
if (key_id == _NM_TEAM_LINK_WATCHER_KEY_NUM) {
nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("'%s' is not a valid key"), key);
return NULL;
}
if (parse_data[key_id].str_val) {
nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("duplicate key '%s'"), key);
return NULL;
}
parse_data[key_id].str_val = val;
if (info->key_type == KEY_TYPE_INT) {
gint64 v64;
v64 = _nm_utils_ascii_str_to_int64(val, 10, G_MININT, G_MAXINT, G_MAXINT64);
if (v64 == G_MAXINT64 && ((errsv = errno) != 0)) {
if (errsv == ERANGE) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
_("number for '%s' is out of range"),
key);
} else {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
_("value for '%s' must be a number"),
key);
}
return NULL;
}
parse_data[key_id].typ_val.vint = v64;
} else if (info->key_type == KEY_TYPE_BOOL) {
int vbool;
vbool = _nm_utils_ascii_str_to_bool(val, -1);
if (vbool == -1) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
_("value for '%s' must be a boolean"),
key);
return NULL;
}
parse_data[key_id].typ_val.vbool = vbool;
}
}
if (!parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val) {
nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, _("missing 'name' attribute"));
return NULL;
}
watcher_type = _team_link_watcher_get_watcher_type_from_name(
parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
if (watcher_type == NM_TEAM_LINK_WATCHER_TYPE_NONE) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
_("invalid 'name' \"%s\""),
parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
return NULL;
}
for (key_id = 0; key_id < _NM_TEAM_LINK_WATCHER_KEY_NUM; key_id++) {
const TeamLinkWatcherKeyInfo *info = &_team_link_watcher_key_infos[key_id];
if (!parse_data[key_id].str_val)
continue;
if (!NM_FLAGS_ALL(info->watcher_type, watcher_type)) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
_("attribute '%s' is invalid for \"%s\""),
info->name,
parse_data[NM_TEAM_LINK_WATCHER_KEY_NAME].str_val);
return NULL;
}
}
switch (watcher_type) {
case NM_TEAM_LINK_WATCHER_TYPE_ETHTOOL:
watcher = nm_team_link_watcher_new_ethtool(
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_UP),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_DELAY_DOWN),
error);
break;
case NM_TEAM_LINK_WATCHER_TYPE_NSNAPING:
watcher = nm_team_link_watcher_new_nsna_ping(
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX),
_parse_data_get_str(parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST),
error);
break;
default:
nm_assert(watcher_type == NM_TEAM_LINK_WATCHER_TYPE_ARPING);
watcher = nm_team_link_watcher_new_arp_ping2(
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_INIT_WAIT),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_INTERVAL),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_MISSED_MAX),
_parse_data_get_int(parse_data, NM_TEAM_LINK_WATCHER_KEY_VLANID),
_parse_data_get_str(parse_data, NM_TEAM_LINK_WATCHER_KEY_TARGET_HOST),
_parse_data_get_str(parse_data, NM_TEAM_LINK_WATCHER_KEY_SOURCE_HOST),
(NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE
| (_parse_data_get_bool(parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_ACTIVE)
? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_ACTIVE
: NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)
| (_parse_data_get_bool(parse_data, NM_TEAM_LINK_WATCHER_KEY_VALIDATE_INACTIVE)
? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_VALIDATE_INACTIVE
: NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)
| (_parse_data_get_bool(parse_data, NM_TEAM_LINK_WATCHER_KEY_SEND_ALWAYS)
? NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_SEND_ALWAYS
: NM_TEAM_LINK_WATCHER_ARP_PING_FLAG_NONE)),
error);
break;
}
#if NM_MORE_ASSERTS > 5
if (watcher) {
gs_free char * str2 = NULL;
nm_auto_unref_team_link_watcher NMTeamLinkWatcher *watcher2 = NULL;
static _nm_thread_local int recursive;
nm_assert(!error || !*error);
if (recursive == 0) {
recursive = 1;
str2 = nm_utils_team_link_watcher_to_string(watcher);
nm_assert(str2);
watcher2 = nm_utils_team_link_watcher_from_string(str2, NULL);
nm_assert(watcher2);
nm_assert(nm_team_link_watcher_equal(watcher, watcher2));
nm_assert(nm_team_link_watcher_equal(watcher2, watcher));
nm_assert(recursive == 1);
recursive = 0;
}
} else
nm_assert(!error || *error);
#endif
return watcher;
}