/* SPDX-License-Identifier: LGPL-2.1-or-later */
#include "nm-default.h"
#include "nmp-rules-manager.h"
#include <linux/fib_rules.h>
#include <linux/rtnetlink.h>
#include "nm-std-aux/c-list-util.h"
#include "nmp-object.h"
/*****************************************************************************/
struct _NMPRulesManager {
NMPlatform *platform;
GHashTable *by_obj;
GHashTable *by_user_tag;
GHashTable *by_data;
guint ref_count;
};
/*****************************************************************************/
static void _rules_init(NMPRulesManager *self);
/*****************************************************************************/
#define _NMLOG_DOMAIN LOGD_PLATFORM
#define _NMLOG_PREFIX_NAME "rules-manager"
#define _NMLOG(level, ...) \
G_STMT_START \
{ \
const NMLogLevel __level = (level); \
\
if (nm_logging_enabled(__level, _NMLOG_DOMAIN)) { \
_nm_log(__level, \
_NMLOG_DOMAIN, \
0, \
NULL, \
NULL, \
"%s: " _NM_UTILS_MACRO_FIRST(__VA_ARGS__), \
_NMLOG_PREFIX_NAME _NM_UTILS_MACRO_REST(__VA_ARGS__)); \
} \
} \
G_STMT_END
/*****************************************************************************/
static gboolean
NMP_IS_RULES_MANAGER(gpointer self)
{
return self && ((NMPRulesManager *) self)->ref_count > 0
&& NM_IS_PLATFORM(((NMPRulesManager *) self)->platform);
}
#define _USER_TAG_LOG(user_tag) nm_hash_obfuscate_ptr(1240261787u, (user_tag))
/*****************************************************************************/
typedef struct {
const NMPObject *obj;
gconstpointer user_tag;
CList obj_lst;
CList user_tag_lst;
/* track_priority_val zero is special: those are weakly tracked rules.
* That means: NetworkManager will restore them only if it removed them earlier.
* But it will not remove or add them otherwise.
*
* Otherwise, the track_priority_val goes together with track_priority_present.
* In case of one rule being tracked multiple times (with different priorities),
* the one with higher priority wins. See _rules_obj_get_best_data().
* Then, the winning present state either enforces that the rule is present
* or absent.
*
* If a rules is not tracked at all, it is ignored by NetworkManager. Assuming
* that it was added externally by the user. But unlike weakly tracked rules,
* NM will *not* restore such rules if NetworkManager themself removed them. */
guint32 track_priority_val;
bool track_priority_present : 1;
bool dirty : 1;
} RulesData;
typedef enum {
CONFIG_STATE_NONE = 0,
CONFIG_STATE_ADDED_BY_US = 1,
CONFIG_STATE_REMOVED_BY_US = 2,
/* ConfigState encodes whether the rule was touched by us at all (CONFIG_STATE_NONE).
*
* Maybe we would only need to track whether we touched the rule at all. But we
* track it more in detail what we did: did we add it (CONFIG_STATE_ADDED_BY_US)
* or did we remove it (CONFIG_STATE_REMOVED_BY_US)?
* Finally, we need CONFIG_STATE_OWNED_BY_US, which means that we didn't actively
* add/remove it, but whenever we are about to undo the add/remove, we need to do it.
* In that sense, CONFIG_STATE_OWNED_BY_US is really just a flag that we unconditionally
* force the state next time when necessary. */
CONFIG_STATE_OWNED_BY_US = 3,
} ConfigState;
typedef struct {
const NMPObject *obj;
CList obj_lst_head;
/* indicates whether we configured/removed the rule (during sync()). We need that, so
* if the rule gets untracked, that we know to remove/restore it.
*
* This makes NMPRulesManager stateful (beyond the configuration that indicates
* which rules are tracked).
* After a restart, NetworkManager would no longer remember which rules were added
* by us.
*
* That is partially fixed by NetworkManager taking over the rules that it
* actively configures (see %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG). */
ConfigState config_state;
} RulesObjData;
typedef struct {
gconstpointer user_tag;
CList user_tag_lst_head;
} RulesUserTagData;
/*****************************************************************************/
static void _rules_data_untrack(NMPRulesManager *self,
RulesData * rules_data,
gboolean remove_user_tag_data,
gboolean make_owned_by_us);
/*****************************************************************************/
static void
_rules_data_assert(const RulesData *rules_data, gboolean linked)
{
nm_assert(rules_data);
nm_assert(NMP_OBJECT_GET_TYPE(rules_data->obj) == NMP_OBJECT_TYPE_ROUTING_RULE);
nm_assert(nmp_object_is_visible(rules_data->obj));
nm_assert(rules_data->user_tag);
nm_assert(!linked || !c_list_is_empty(&rules_data->obj_lst));
nm_assert(!linked || !c_list_is_empty(&rules_data->user_tag_lst));
}
static guint
_rules_data_hash(gconstpointer data)
{
const RulesData *rules_data = data;
NMHashState h;
_rules_data_assert(rules_data, FALSE);
nm_hash_init(&h, 269297543u);
nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(rules_data->obj),
NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
&h);
nm_hash_update_val(&h, rules_data->user_tag);
return nm_hash_complete(&h);
}
static gboolean
_rules_data_equal(gconstpointer data_a, gconstpointer data_b)
{
const RulesData *rules_data_a = data_a;
const RulesData *rules_data_b = data_b;
_rules_data_assert(rules_data_a, FALSE);
_rules_data_assert(rules_data_b, FALSE);
return rules_data_a->user_tag == rules_data_b->user_tag
&& (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(rules_data_a->obj),
NMP_OBJECT_CAST_ROUTING_RULE(rules_data_b->obj),
NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID)
== 0);
}
static void
_rules_data_destroy(gpointer data)
{
RulesData *rules_data = data;
_rules_data_assert(rules_data, FALSE);
c_list_unlink_stale(&rules_data->obj_lst);
c_list_unlink_stale(&rules_data->user_tag_lst);
nmp_object_unref(rules_data->obj);
g_slice_free(RulesData, rules_data);
}
static const RulesData *
_rules_obj_get_best_data(RulesObjData *obj_data)
{
RulesData * rules_data;
const RulesData *rd_best = NULL;
c_list_for_each_entry (rules_data, &obj_data->obj_lst_head, obj_lst) {
_rules_data_assert(rules_data, TRUE);
if (rd_best) {
if (rd_best->track_priority_val > rules_data->track_priority_val)
continue;
if (rd_best->track_priority_val == rules_data->track_priority_val) {
if (rd_best->track_priority_present || !rules_data->track_priority_present) {
/* if the priorities are identical, then "present" wins over
* "!present" (absent). */
continue;
}
}
}
rd_best = rules_data;
}
return rd_best;
}
static guint
_rules_obj_hash(gconstpointer data)
{
const RulesObjData *obj_data = data;
NMHashState h;
nm_hash_init(&h, 432817559u);
nm_platform_routing_rule_hash_update(NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj),
NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID,
&h);
return nm_hash_complete(&h);
}
static gboolean
_rules_obj_equal(gconstpointer data_a, gconstpointer data_b)
{
const RulesObjData *obj_data_a = data_a;
const RulesObjData *obj_data_b = data_b;
return (nm_platform_routing_rule_cmp(NMP_OBJECT_CAST_ROUTING_RULE(obj_data_a->obj),
NMP_OBJECT_CAST_ROUTING_RULE(obj_data_b->obj),
NM_PLATFORM_ROUTING_RULE_CMP_TYPE_ID)
== 0);
}
static void
_rules_obj_destroy(gpointer data)
{
RulesObjData *obj_data = data;
c_list_unlink_stale(&obj_data->obj_lst_head);
nmp_object_unref(obj_data->obj);
g_slice_free(RulesObjData, obj_data);
}
static guint
_rules_user_tag_hash(gconstpointer data)
{
const RulesUserTagData *user_tag_data = data;
return nm_hash_val(644693447u, user_tag_data->user_tag);
}
static gboolean
_rules_user_tag_equal(gconstpointer data_a, gconstpointer data_b)
{
const RulesUserTagData *user_tag_data_a = data_a;
const RulesUserTagData *user_tag_data_b = data_b;
return user_tag_data_a->user_tag == user_tag_data_b->user_tag;
}
static void
_rules_user_tag_destroy(gpointer data)
{
RulesUserTagData *user_tag_data = data;
c_list_unlink_stale(&user_tag_data->user_tag_lst_head);
g_slice_free(RulesUserTagData, user_tag_data);
}
static RulesData *
_rules_data_lookup(GHashTable *by_data, const NMPObject *obj, gconstpointer user_tag)
{
RulesData rules_data_needle = {
.obj = obj,
.user_tag = user_tag,
};
return g_hash_table_lookup(by_data, &rules_data_needle);
}
/**
* nmp_rules_manager_track:
* @self: the #NMPRulesManager instance
* @routing_rule: the #NMPlatformRoutingRule to track or untrack
* @track_priority: the priority for tracking the rule. Note that
* negative values indicate a forced absence of the rule. Priorities
* are compared with their absolute values (with higher absolute
* value being more important). For example, if you track the same
* rule twice, once with priority -5 and +10, then the rule is
* present (because the positive number is more important).
* The special value 0 indicates weakly-tracked rules.
* @user_tag: the tag associated with tracking this rule. The same tag
* must be used to untrack the rule later.
* @user_tag_untrack: if not %NULL, at the same time untrack this user-tag
* for the same rule. Note that this is different from a plain nmp_rules_manager_untrack(),
* because it enforces ownership of the now tracked rule. On the other hand,
* a plain nmp_rules_manager_untrack() merely forgets about the tracking.
* The purpose here is to set this to %NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG.
*/
void
nmp_rules_manager_track(NMPRulesManager * self,
const NMPlatformRoutingRule *routing_rule,
gint32 track_priority,
gconstpointer user_tag,
gconstpointer user_tag_untrack)
{
NMPObject obj_stack;
const NMPObject * p_obj_stack;
RulesData * rules_data;
RulesObjData * obj_data;
RulesUserTagData *user_tag_data;
gboolean changed = FALSE;
guint32 track_priority_val;
gboolean track_priority_present;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
g_return_if_fail(routing_rule);
g_return_if_fail(user_tag);
nm_assert(track_priority != G_MININT32);
_rules_init(self);
p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
nm_assert(nmp_object_is_visible(p_obj_stack));
if (track_priority >= 0) {
track_priority_val = track_priority;
track_priority_present = TRUE;
} else {
track_priority_val = -track_priority;
track_priority_present = FALSE;
}
rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag);
if (!rules_data) {
rules_data = g_slice_new(RulesData);
*rules_data = (RulesData){
.obj = nm_dedup_multi_index_obj_intern(nm_platform_get_multi_idx(self->platform),
p_obj_stack),
.user_tag = user_tag,
.track_priority_val = track_priority_val,
.track_priority_present = track_priority_present,
.dirty = FALSE,
};
g_hash_table_add(self->by_data, rules_data);
obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj);
if (!obj_data) {
obj_data = g_slice_new(RulesObjData);
*obj_data = (RulesObjData){
.obj = nmp_object_ref(rules_data->obj),
.obj_lst_head = C_LIST_INIT(obj_data->obj_lst_head),
.config_state = CONFIG_STATE_NONE,
};
g_hash_table_add(self->by_obj, obj_data);
}
c_list_link_tail(&obj_data->obj_lst_head, &rules_data->obj_lst);
user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag);
if (!user_tag_data) {
user_tag_data = g_slice_new(RulesUserTagData);
*user_tag_data = (RulesUserTagData){
.user_tag = user_tag,
.user_tag_lst_head = C_LIST_INIT(user_tag_data->user_tag_lst_head),
};
g_hash_table_add(self->by_user_tag, user_tag_data);
}
c_list_link_tail(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst);
changed = TRUE;
} else {
rules_data->dirty = FALSE;
if (rules_data->track_priority_val != track_priority_val
|| rules_data->track_priority_present != track_priority_present) {
rules_data->track_priority_val = track_priority_val;
rules_data->track_priority_present = track_priority_present;
changed = TRUE;
}
}
if (user_tag_untrack) {
if (user_tag != user_tag_untrack) {
RulesData *rules_data_untrack;
rules_data_untrack = _rules_data_lookup(self->by_data, p_obj_stack, user_tag_untrack);
if (rules_data_untrack)
_rules_data_untrack(self, rules_data_untrack, FALSE, TRUE);
} else
nm_assert_not_reached();
}
_rules_data_assert(rules_data, TRUE);
if (changed) {
_LOGD("routing-rule: track [" NM_HASH_OBFUSCATE_PTR_FMT ",%s%u] \"%s\")",
_USER_TAG_LOG(rules_data->user_tag),
(rules_data->track_priority_val == 0
? ""
: (rules_data->track_priority_present ? "+" : "-")),
(guint) rules_data->track_priority_val,
nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
}
}
static void
_rules_data_untrack(NMPRulesManager *self,
RulesData * rules_data,
gboolean remove_user_tag_data,
gboolean make_owned_by_us)
{
RulesObjData *obj_data;
nm_assert(NMP_IS_RULES_MANAGER(self));
_rules_data_assert(rules_data, TRUE);
nm_assert(self->by_data);
nm_assert(g_hash_table_lookup(self->by_data, rules_data) == rules_data);
_LOGD("routing-rule: untrack [" NM_HASH_OBFUSCATE_PTR_FMT "] \"%s\"",
_USER_TAG_LOG(rules_data->user_tag),
nmp_object_to_string(rules_data->obj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
#if NM_MORE_ASSERTS
{
RulesUserTagData *user_tag_data;
user_tag_data = g_hash_table_lookup(self->by_user_tag, &rules_data->user_tag);
nm_assert(user_tag_data);
nm_assert(c_list_contains(&user_tag_data->user_tag_lst_head, &rules_data->user_tag_lst));
}
#endif
nm_assert(!c_list_is_empty(&rules_data->user_tag_lst));
obj_data = g_hash_table_lookup(self->by_obj, &rules_data->obj);
nm_assert(obj_data);
nm_assert(c_list_contains(&obj_data->obj_lst_head, &rules_data->obj_lst));
nm_assert(obj_data == g_hash_table_lookup(self->by_obj, &rules_data->obj));
if (make_owned_by_us) {
if (obj_data->config_state == CONFIG_STATE_NONE) {
/* we need to mark this entry that it requires a touch on the next
* sync. */
obj_data->config_state = CONFIG_STATE_OWNED_BY_US;
}
} else if (remove_user_tag_data && c_list_length_is(&rules_data->user_tag_lst, 1))
g_hash_table_remove(self->by_user_tag, &rules_data->user_tag);
/* if obj_data is marked to be "added_by_us" or "removed_by_us", we need to keep this entry
* around for the next sync -- so that we can undo what we did earlier. */
if (obj_data->config_state == CONFIG_STATE_NONE && c_list_length_is(&rules_data->obj_lst, 1))
g_hash_table_remove(self->by_obj, &rules_data->obj);
g_hash_table_remove(self->by_data, rules_data);
}
void
nmp_rules_manager_untrack(NMPRulesManager * self,
const NMPlatformRoutingRule *routing_rule,
gconstpointer user_tag)
{
NMPObject obj_stack;
const NMPObject *p_obj_stack;
RulesData * rules_data;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
g_return_if_fail(routing_rule);
g_return_if_fail(user_tag);
_rules_init(self);
p_obj_stack = nmp_object_stackinit(&obj_stack, NMP_OBJECT_TYPE_ROUTING_RULE, routing_rule);
nm_assert(nmp_object_is_visible(p_obj_stack));
rules_data = _rules_data_lookup(self->by_data, p_obj_stack, user_tag);
if (rules_data)
_rules_data_untrack(self, rules_data, TRUE, FALSE);
}
void
nmp_rules_manager_set_dirty(NMPRulesManager *self, gconstpointer user_tag)
{
RulesData * rules_data;
RulesUserTagData *user_tag_data;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
g_return_if_fail(user_tag);
if (!self->by_data)
return;
user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag);
if (!user_tag_data)
return;
c_list_for_each_entry (rules_data, &user_tag_data->user_tag_lst_head, user_tag_lst)
rules_data->dirty = TRUE;
}
void
nmp_rules_manager_untrack_all(NMPRulesManager *self,
gconstpointer user_tag,
gboolean all /* or only dirty */)
{
RulesData * rules_data;
RulesData * rules_data_safe;
RulesUserTagData *user_tag_data;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
g_return_if_fail(user_tag);
if (!self->by_data)
return;
user_tag_data = g_hash_table_lookup(self->by_user_tag, &user_tag);
if (!user_tag_data)
return;
c_list_for_each_entry_safe (rules_data,
rules_data_safe,
&user_tag_data->user_tag_lst_head,
user_tag_lst) {
if (all || rules_data->dirty)
_rules_data_untrack(self, rules_data, FALSE, FALSE);
}
if (c_list_is_empty(&user_tag_data->user_tag_lst_head))
g_hash_table_remove(self->by_user_tag, user_tag_data);
}
void
nmp_rules_manager_sync(NMPRulesManager *self, gboolean keep_deleted_rules)
{
const NMDedupMultiHeadEntry *pl_head_entry;
NMDedupMultiIter pl_iter;
const NMPObject * plobj;
gs_unref_ptrarray GPtrArray *rules_to_delete = NULL;
RulesObjData * obj_data;
GHashTableIter h_iter;
guint i;
const RulesData * rd_best;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
if (!self->by_data)
return;
_LOGD("sync%s", keep_deleted_rules ? " (don't remove any rules)" : "");
pl_head_entry = nm_platform_lookup_obj_type(self->platform, NMP_OBJECT_TYPE_ROUTING_RULE);
if (pl_head_entry) {
nmp_cache_iter_for_each (&pl_iter, pl_head_entry, &plobj) {
obj_data = g_hash_table_lookup(self->by_obj, &plobj);
if (!obj_data) {
/* this rule is not tracked. It was externally added, hence we
* ignore it. */
continue;
}
rd_best = _rules_obj_get_best_data(obj_data);
if (rd_best) {
if (rd_best->track_priority_present) {
if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
continue;
}
if (rd_best->track_priority_val == 0) {
if (!NM_IN_SET(obj_data->config_state,
CONFIG_STATE_ADDED_BY_US,
CONFIG_STATE_OWNED_BY_US)) {
obj_data->config_state = CONFIG_STATE_NONE;
continue;
}
obj_data->config_state = CONFIG_STATE_NONE;
}
}
if (keep_deleted_rules) {
_LOGD("forget/leak rule added by us: %s",
nmp_object_to_string(plobj, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
continue;
}
if (!rules_to_delete)
rules_to_delete = g_ptr_array_new_with_free_func((GDestroyNotify) nmp_object_unref);
g_ptr_array_add(rules_to_delete, (gpointer) nmp_object_ref(plobj));
obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
}
}
if (rules_to_delete) {
for (i = 0; i < rules_to_delete->len; i++)
nm_platform_object_delete(self->platform, rules_to_delete->pdata[i]);
}
g_hash_table_iter_init(&h_iter, self->by_obj);
while (g_hash_table_iter_next(&h_iter, (gpointer *) &obj_data, NULL)) {
rd_best = _rules_obj_get_best_data(obj_data);
if (!rd_best) {
g_hash_table_iter_remove(&h_iter);
continue;
}
if (!rd_best->track_priority_present) {
if (obj_data->config_state == CONFIG_STATE_OWNED_BY_US)
obj_data->config_state = CONFIG_STATE_REMOVED_BY_US;
continue;
}
if (rd_best->track_priority_val == 0) {
if (!NM_IN_SET(obj_data->config_state,
CONFIG_STATE_REMOVED_BY_US,
CONFIG_STATE_OWNED_BY_US)) {
obj_data->config_state = CONFIG_STATE_NONE;
continue;
}
obj_data->config_state = CONFIG_STATE_NONE;
}
plobj =
nm_platform_lookup_obj(self->platform, NMP_CACHE_ID_TYPE_OBJECT_TYPE, obj_data->obj);
if (plobj)
continue;
obj_data->config_state = CONFIG_STATE_ADDED_BY_US;
nm_platform_routing_rule_add(self->platform,
NMP_NLM_FLAG_ADD,
NMP_OBJECT_CAST_ROUTING_RULE(obj_data->obj));
}
}
void
nmp_rules_manager_track_from_platform(NMPRulesManager *self,
NMPlatform * platform,
int addr_family,
gint32 tracking_priority,
gconstpointer user_tag)
{
NMPLookup lookup;
const NMDedupMultiHeadEntry *head_entry;
NMDedupMultiIter iter;
const NMPObject * o;
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
if (!platform)
platform = self->platform;
else
g_return_if_fail(NM_IS_PLATFORM(platform));
nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
nmp_lookup_init_obj_type(&lookup, NMP_OBJECT_TYPE_ROUTING_RULE);
head_entry = nm_platform_lookup(platform, &lookup);
nmp_cache_iter_for_each (&iter, head_entry, &o) {
const NMPlatformRoutingRule *rr = NMP_OBJECT_CAST_ROUTING_RULE(o);
if (addr_family != AF_UNSPEC && rr->addr_family != addr_family)
continue;
nmp_rules_manager_track(self, rr, tracking_priority, user_tag, NULL);
}
}
/*****************************************************************************/
void
nmp_rules_manager_track_default(NMPRulesManager *self,
int addr_family,
gint32 track_priority,
gconstpointer user_tag)
{
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
nm_assert(NM_IN_SET(addr_family, AF_UNSPEC, AF_INET, AF_INET6));
/* track the default rules. See also `man ip-rule`. */
if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET)) {
nmp_rules_manager_track(self,
&((NMPlatformRoutingRule){
.addr_family = AF_INET,
.priority = 0,
.table = RT_TABLE_LOCAL,
.action = FR_ACT_TO_TBL,
.protocol = RTPROT_KERNEL,
}),
track_priority,
user_tag,
NULL);
nmp_rules_manager_track(self,
&((NMPlatformRoutingRule){
.addr_family = AF_INET,
.priority = 32766,
.table = RT_TABLE_MAIN,
.action = FR_ACT_TO_TBL,
.protocol = RTPROT_KERNEL,
}),
track_priority,
user_tag,
NULL);
nmp_rules_manager_track(self,
&((NMPlatformRoutingRule){
.addr_family = AF_INET,
.priority = 32767,
.table = RT_TABLE_DEFAULT,
.action = FR_ACT_TO_TBL,
.protocol = RTPROT_KERNEL,
}),
track_priority,
user_tag,
NULL);
}
if (NM_IN_SET(addr_family, AF_UNSPEC, AF_INET6)) {
nmp_rules_manager_track(self,
&((NMPlatformRoutingRule){
.addr_family = AF_INET6,
.priority = 0,
.table = RT_TABLE_LOCAL,
.action = FR_ACT_TO_TBL,
.protocol = RTPROT_KERNEL,
}),
track_priority,
user_tag,
NULL);
nmp_rules_manager_track(self,
&((NMPlatformRoutingRule){
.addr_family = AF_INET6,
.priority = 32766,
.table = RT_TABLE_MAIN,
.action = FR_ACT_TO_TBL,
.protocol = RTPROT_KERNEL,
}),
track_priority,
user_tag,
NULL);
}
}
static void
_rules_init(NMPRulesManager *self)
{
if (self->by_data)
return;
self->by_data =
g_hash_table_new_full(_rules_data_hash, _rules_data_equal, NULL, _rules_data_destroy);
self->by_obj =
g_hash_table_new_full(_rules_obj_hash, _rules_obj_equal, NULL, _rules_obj_destroy);
self->by_user_tag = g_hash_table_new_full(_rules_user_tag_hash,
_rules_user_tag_equal,
NULL,
_rules_user_tag_destroy);
}
/*****************************************************************************/
NMPRulesManager *
nmp_rules_manager_new(NMPlatform *platform)
{
NMPRulesManager *self;
g_return_val_if_fail(NM_IS_PLATFORM(platform), NULL);
self = g_slice_new(NMPRulesManager);
*self = (NMPRulesManager){
.ref_count = 1,
.platform = g_object_ref(platform),
};
return self;
}
void
nmp_rules_manager_ref(NMPRulesManager *self)
{
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
self->ref_count++;
}
void
nmp_rules_manager_unref(NMPRulesManager *self)
{
g_return_if_fail(NMP_IS_RULES_MANAGER(self));
if (--self->ref_count > 0)
return;
if (self->by_data) {
g_hash_table_destroy(self->by_user_tag);
g_hash_table_destroy(self->by_obj);
g_hash_table_destroy(self->by_data);
}
g_object_unref(self->platform);
g_slice_free(NMPRulesManager, self);
}