/* SPDX-License-Identifier: LGPL-2.1-or-later */ #ifndef __NM_L3CFG_H__ #define __NM_L3CFG_H__ #include "libnm-platform/nmp-object.h" #include "nm-l3-config-data.h" #define NM_L3CFG_CONFIG_PRIORITY_IPV4LL 0 #define NM_L3CFG_CONFIG_PRIORITY_IPV6LL 1 #define NM_L3CFG_CONFIG_PRIORITY_VPN 9 #define NM_ACD_TIMEOUT_RFC5227_MSEC 9000u #define NM_ACD_TIMEOUT_MAX_MSEC 30000u #define NM_TYPE_L3CFG (nm_l3cfg_get_type()) #define NM_L3CFG(obj) (_NM_G_TYPE_CHECK_INSTANCE_CAST((obj), NM_TYPE_L3CFG, NML3Cfg)) #define NM_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST((klass), NM_TYPE_L3CFG, NML3CfgClass)) #define NM_IS_L3CFG(obj) (G_TYPE_CHECK_INSTANCE_TYPE((obj), NM_TYPE_L3CFG)) #define NM_IS_L3CFG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE((klass), NM_TYPE_L3CFG)) #define NM_L3CFG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS((obj), NM_TYPE_L3CFG, NML3CfgClass)) #define NM_L3CFG_NETNS "netns" #define NM_L3CFG_IFINDEX "ifindex" #define NM_L3CFG_SIGNAL_NOTIFY "l3cfg-notify" #define NM_DNS_ROUTES_FWMARK_TABLE_PRIO 20053 typedef enum _nm_packed { _NM_L3_ACD_DEFEND_TYPE_NONE, NM_L3_ACD_DEFEND_TYPE_NEVER, NM_L3_ACD_DEFEND_TYPE_ONCE, NM_L3_ACD_DEFEND_TYPE_ALWAYS, } NML3AcdDefendType; /** * NML3CfgConfigFlags: * @NM_L3CFG_CONFIG_FLAGS_NONE: no flags, the default. * @NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD: if this flag is set, * the NML3ConfigData doesn't get merged and the information won't be * synced. The only purpose is to run ACD on its IPv4 addresses, but * regardless whether ACD succeeds/fails, the IP addresses won't be configured. * The point is to run ACD first (without configuring it), and only * commit the settings if requested. That can either happen by * nm_l3cfg_add_config() the same NML3Cfg again (with a different * tag), or by calling nm_l3cfg_add_config() again with this flag * cleared (and the same tag). * @NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE: a commit with * %NM_L3_CFG_COMMIT_TYPE_ASSUME, means to not remove/add * addresses that are missing/already exist. The assume mode * is for taking over a device gracefully after restart, so * it aims to preserve whatever was configured (or not configured). * With this flag enabled, the first commit in assume mode will still * add the addresses/routes. This is necessary for example with IPv6LL. * Also while assuming a device, we want to configure things * (like an IPv6 address), so we need to bypass the common * "don't change" behavior. At least once. If the address/route * is still not (no longer) configured on the subsequent * commit, it's not getting added again. */ typedef enum _nm_packed { NM_L3CFG_CONFIG_FLAGS_NONE = 0, NM_L3CFG_CONFIG_FLAGS_ONLY_FOR_ACD = (1LL << 0), NM_L3CFG_CONFIG_FLAGS_ASSUME_CONFIG_ONCE = (1LL << 1), } NML3CfgConfigFlags; typedef enum _nm_packed { NM_L3_ACD_ADDR_STATE_INIT, NM_L3_ACD_ADDR_STATE_PROBING, NM_L3_ACD_ADDR_STATE_USED, NM_L3_ACD_ADDR_STATE_READY, NM_L3_ACD_ADDR_STATE_DEFENDING, NM_L3_ACD_ADDR_STATE_CONFLICT, NM_L3_ACD_ADDR_STATE_EXTERNAL_REMOVED, } NML3AcdAddrState; typedef struct { const NMPObject *obj; const NML3ConfigData *l3cd; gconstpointer tag; struct { guint32 acd_timeout_msec_track; NML3AcdDefendType acd_defend_type_track; bool acd_dirty_track : 1; bool acd_failed_notified_track : 1; } _priv; } NML3AcdAddrTrackInfo; typedef struct { in_addr_t addr; guint n_track_infos; NML3AcdAddrState state; NML3Cfg *l3cfg; const NML3AcdAddrTrackInfo *track_infos; NMEtherAddr last_conflict_addr; } NML3AcdAddrInfo; static inline const NML3AcdAddrTrackInfo * nm_l3_acd_addr_info_find_track_info(const NML3AcdAddrInfo *addr_info, gconstpointer tag, const NML3ConfigData *l3cd, const NMPObject *obj) { guint i; const NML3AcdAddrTrackInfo *ti; nm_assert(addr_info); /* we always expect that the number n_track_infos is reasonably small. Hence, * a naive linear search is simplest and fastest (e.g. we don't have a hash table). */ for (i = 0, ti = addr_info->track_infos; i < addr_info->n_track_infos; i++, ti++) { if (l3cd && ti->l3cd != l3cd) continue; if (tag && ti->tag != tag) continue; if (obj && ti->obj != obj) continue; return ti; } return NULL; } typedef enum { NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT, /* Emitted before the merged l3cd is committed to platform. * Note that this gets emitted "under unsafe circumstances". That means, * you should not perform complex operations inside this callback, * and neither should you call into NML3Cfg again (reentrancy). */ NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT, /* emitted at the end of nm_l3cfg_platform_commit(). This signals also that * nm_l3cfg_is_ready() might have switched to TRUE. Also emitted * "under unsafe circumstances". */ NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT, /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes. * It re-emits the platform signal. * Contrary to NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, this even * is re-emitted synchronously. You probably want to hook to the on-idle signal, * unless you need to catch all intermediate changes too. Note that this * event is not re-entrant safe (so beware what you are doing). */ NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE, /* NML3Cfg hooks to the NMPlatform signals for link, addresses and routes. * It re-emits the signal on an idle handler. The purpose is for something * like NMDevice which is already subscribed to these signals, it can get the * notifications without also subscribing directly to the platform. */ NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE, NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT, _NM_L3_CONFIG_NOTIFY_TYPE_NUM, } NML3ConfigNotifyType; struct _NML3IPv4LL; typedef struct { NML3ConfigNotifyType notify_type; union { struct { const NML3ConfigData *l3cd_old; const NML3ConfigData *l3cd_new; bool l3cd_changed; } commit; struct { NML3AcdAddrInfo info; } acd_event; struct { const NMPObject *obj; NMPlatformSignalChangeType change_type; } platform_change; struct { guint64 obj_type_flags; } platform_change_on_idle; struct { struct _NML3IPv4LL *ipv4ll; } ipv4ll_event; }; } NML3ConfigNotifyData; struct _NML3CfgPrivate; struct _NMPGlobalTracker; struct _NML3Cfg { GObject parent; struct { struct _NML3CfgPrivate *p; NMNetns *netns; NMPlatform *platform; struct _NMPGlobalTracker *global_tracker; const NMPObject *plobj; const NMPObject *plobj_next; int ifindex; } priv; /* NML3Cfg strongly cooperates with NMNetns. The latter is * the one that creates and manages (also) the lifetime of the * NML3Cfg instance. We track some per-l3cfg-data that is only * relevant to NMNetns here. */ struct { guint64 signal_pending_obj_type_flags; CList signal_pending_lst; CList ecmp_track_ifindex_lst_head; } internal_netns; }; typedef struct _NML3CfgClass NML3CfgClass; GType nm_l3cfg_get_type(void); NML3Cfg *nm_l3cfg_new(NMNetns *netns, int ifindex); /*****************************************************************************/ gboolean nm_l3cfg_is_ready(NML3Cfg *self); void _nm_l3cfg_notify_platform_change_on_idle(NML3Cfg *self, guint64 obj_type_flags); void _nm_l3cfg_notify_platform_change(NML3Cfg *self, NMPlatformSignalChangeType change_type, const NMPObject *obj); /*****************************************************************************/ struct _NMDedupMultiIndex; struct _NMDedupMultiIndex *nm_netns_get_multi_idx(NMNetns *self); static inline struct _NMDedupMultiIndex * nm_l3cfg_get_multi_idx(const NML3Cfg *self) { g_return_val_if_fail(NM_IS_L3CFG(self), NULL); return nm_netns_get_multi_idx(self->priv.netns); } /*****************************************************************************/ static inline int nm_l3cfg_get_ifindex(const NML3Cfg *self) { g_return_val_if_fail(NM_IS_L3CFG(self), 0); return self->priv.ifindex; } static inline const NMPObject * nm_l3cfg_get_plobj(const NML3Cfg *self, gboolean get_next) { if (!self) return NULL; nm_assert(NM_IS_L3CFG(self)); if (get_next) { /* This is the instance that we just got reported in the last signal from * the platform cache. It's probably exactly the same as if you would look * into the platform cache. * * On the other hand, we pick up changes only on an idle handler. So the last * decisions were not made based on this, but instead of "plobj". */ return self->priv.plobj_next; } return self->priv.plobj; } static inline const NMPlatformLink * nm_l3cfg_get_pllink(const NML3Cfg *self, gboolean get_next) { return NMP_OBJECT_CAST_LINK(nm_l3cfg_get_plobj(self, get_next)); } static inline const char * nm_l3cfg_get_ifname(const NML3Cfg *self, gboolean get_next) { return nmp_object_link_get_ifname(nm_l3cfg_get_plobj(self, get_next)); } gboolean nm_l3cfg_is_vrf(const NML3Cfg *self); static inline NMNetns * nm_l3cfg_get_netns(const NML3Cfg *self) { nm_assert(NM_IS_L3CFG(self)); return self->priv.netns; } static inline NMPlatform * nm_l3cfg_get_platform(const NML3Cfg *self) { nm_assert(NM_IS_L3CFG(self)); return self->priv.platform; } /*****************************************************************************/ void _nm_l3cfg_emit_signal_notify(NML3Cfg *self, const NML3ConfigNotifyData *notify_data); /*****************************************************************************/ void nm_l3cfg_mark_config_dirty(NML3Cfg *self, gconstpointer tag, gboolean dirty); gboolean nm_l3cfg_add_config(NML3Cfg *self, gconstpointer tag, gboolean replace_same_tag, const NML3ConfigData *l3cd, int priority, guint32 default_route_table_4, guint32 default_route_table_6, guint32 default_route_metric_4, guint32 default_route_metric_6, guint32 default_route_penalty_4, guint32 default_route_penalty_6, int default_dns_priority_4, int default_dns_priority_6, NML3AcdDefendType acd_defend_type, guint32 acd_timeout_msec, NML3CfgConfigFlags config_flags, NML3ConfigMergeFlags merge_flags); gboolean nm_l3cfg_remove_config(NML3Cfg *self, gconstpointer tag, const NML3ConfigData *l3cd); gboolean nm_l3cfg_remove_config_all(NML3Cfg *self, gconstpointer tag); gboolean nm_l3cfg_remove_config_all_dirty(NML3Cfg *self, gconstpointer tag); /*****************************************************************************/ /* DOC(l3cfg:commit-type): * * The major idea of NML3Cfg is that independent parties can register configuration * (NML3ConfigData via nm_l3cfg_add_config()), and then nm_l3cfg_commit() will * actually configure it. Usually we would not call the synchronous nm_l3cfg_commit(), * but instead nm_l3cfg_commit_on_idle_schedule(). * * We have different levels of "how much" we should sync during commit. That is * NML3CfgCommitType. Since independent parties should be able to work together, * they can only ask for their minimal required commit-type level. That means, * during commit we will commit with the highest level of how much one of the * users request the commit. To request a commit level, users can call * nm_l3cfg_commit_type_register(). nm_l3cfg_commit_on_idle_schedule() also * accepts a one-time commit-type argument. * * This is related to NMDevice's managed_type, which we use to control whether * to touch/assume/manage the interface. * * The numeric values of the enum matters: higher number mean more "important". * E.g. "assume" tries to preserve the most settings, while "reapply" forces * all configuration to match. */ typedef enum _nm_packed { /* the NML3Cfg instance tracks with nm_l3cfg_commit_setup_register() the requested commit type. * Use _NM_L3_CFG_COMMIT_TYPE_AUTO to automatically choose the level as requested. */ NM_L3_CFG_COMMIT_TYPE_AUTO, /* Don't touch the interface. */ NM_L3_CFG_COMMIT_TYPE_NONE, /* UPDATE means to add new addresses/routes, while also removing addresses/routes * that are no longer present (but were previously configured by NetworkManager). * Routes/addresses that were removed externally won't be re-added, and routes/addresses * that are added externally won't be removed. */ NM_L3_CFG_COMMIT_TYPE_UPDATE, /* This is a full sync. It configures the IP addresses/routes that are indicated, * while removing the existing ones from the interface. */ NM_L3_CFG_COMMIT_TYPE_REAPPLY, } NML3CfgCommitType; void nm_l3cfg_commit(NML3Cfg *self, NML3CfgCommitType commit_type); gboolean nm_l3cfg_commit_on_idle_schedule(NML3Cfg *self, NML3CfgCommitType commit_type); gboolean nm_l3cfg_commit_on_idle_is_scheduled(NML3Cfg *self); /*****************************************************************************/ gboolean nm_l3cfg_get_acd_is_pending(NML3Cfg *self); const NML3AcdAddrInfo *nm_l3cfg_get_acd_addr_info(NML3Cfg *self, in_addr_t addr); /*****************************************************************************/ typedef enum { NM_L3CFG_CHECK_READY_FLAGS_NONE = 0, NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY = (1ull << 0), NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY = (1ull << 1), } NML3CfgCheckReadyFlags; gboolean nm_l3cfg_check_ready(NML3Cfg *self, const NML3ConfigData *l3cd, int addr_family, NML3CfgCheckReadyFlags flags, GArray **conflicts); gboolean nm_l3cfg_has_failedobj_pending(NML3Cfg *self, int addr_family); /*****************************************************************************/ NML3CfgCommitType nm_l3cfg_commit_type_get(NML3Cfg *self); typedef struct _NML3CfgCommitTypeHandle NML3CfgCommitTypeHandle; NML3CfgCommitTypeHandle *nm_l3cfg_commit_type_register(NML3Cfg *self, NML3CfgCommitType commit_type, NML3CfgCommitTypeHandle *existing_handle, const char *source); void nm_l3cfg_commit_type_unregister(NML3Cfg *self, NML3CfgCommitTypeHandle *handle); static inline gboolean nm_l3cfg_commit_type_clear(NML3Cfg *self, NML3CfgCommitTypeHandle **handle) { if (!handle || !*handle) return FALSE; nm_l3cfg_commit_type_unregister(self, g_steal_pointer(handle)); return TRUE; } void nm_l3cfg_commit_type_reset_update(NML3Cfg *self); /*****************************************************************************/ const NML3ConfigData *nm_l3cfg_get_combined_l3cd(NML3Cfg *self, gboolean get_commited); const NMPObject * nm_l3cfg_get_best_default_route(NML3Cfg *self, int addr_family, gboolean get_commited); in_addr_t *nm_l3cfg_get_configured_ip4_addresses(NML3Cfg *self, gsize *out_len); /*****************************************************************************/ gboolean nm_l3cfg_has_commited_ip6_addresses_pending_dad(NML3Cfg *self); /*****************************************************************************/ struct _NML3IPv4LL *nm_l3cfg_get_ipv4ll(NML3Cfg *self); struct _NML3IPv4LL *nm_l3cfg_access_ipv4ll(NML3Cfg *self); void _nm_l3cfg_unregister_ipv4ll(NML3Cfg *self); /*****************************************************************************/ struct _NMIPConfig; struct _NMIPConfig *nm_l3cfg_ipconfig_get(NML3Cfg *self, int addr_family); struct _NMIPConfig *nm_l3cfg_ipconfig_acquire(NML3Cfg *self, int addr_family); /*****************************************************************************/ typedef struct _NML3CfgBlockHandle NML3CfgBlockHandle; NML3CfgBlockHandle *nm_l3cfg_block_obj_pruning(NML3Cfg *self, int addr_family); void nm_l3cfg_unblock_obj_pruning(NML3CfgBlockHandle *handle); #endif /* __NM_L3CFG_H__ */