NetworkManager/src/core/devices/nm-device.c
Thomas Haller 69376e20a5
core: rename nm_utils_host_id_get_timestamp_ns() to "nsec"
We change more and more to prefer "nsec" (and "usec", "msec", and "sec")
as abbreviations, instead of "ns" (and "us", "ms", "s"). Rename.
2022-03-11 09:43:02 +01:00

18068 lines
653 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* SPDX-License-Identifier: GPL-2.0-or-later */
/*
* Copyright (C) 2005 - 2018 Red Hat, Inc.
* Copyright (C) 2006 - 2008 Novell, Inc.
*/
#include "src/core/nm-default-daemon.h"
#include "nm-device.h"
#include <unistd.h>
#include <sys/ioctl.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/if_ether.h>
#include <linux/if.h>
#include <linux/if_addr.h>
#include <linux/rtnetlink.h>
#include <linux/if_ether.h>
#include <linux/if_infiniband.h>
#include "libnm-std-aux/unaligned.h"
#include "libnm-glib-aux/nm-uuid.h"
#include "libnm-glib-aux/nm-dedup-multi.h"
#include "libnm-glib-aux/nm-random-utils.h"
#include "libnm-systemd-shared/nm-sd-utils-shared.h"
#include "libnm-base/nm-ethtool-base.h"
#include "libnm-core-aux-intern/nm-common-macros.h"
#include "nm-device-private.h"
#include "nm-l3cfg.h"
#include "nm-l3-config-data.h"
#include "nm-l3-ipv4ll.h"
#include "nm-l3-ipv6ll.h"
#include "NetworkManagerUtils.h"
#include "nm-manager.h"
#include "libnm-platform/nm-platform.h"
#include "libnm-platform/nm-platform-utils.h"
#include "libnm-platform/nmp-object.h"
#include "libnm-platform/nmp-route-manager.h"
#include "ndisc/nm-ndisc.h"
#include "ndisc/nm-lndp-ndisc.h"
#include "dhcp/nm-dhcp-manager.h"
#include "dhcp/nm-dhcp-utils.h"
#include "nm-act-request.h"
#include "nm-pacrunner-manager.h"
#include "dnsmasq/nm-dnsmasq-manager.h"
#include "nm-ip-config.h"
#include "nm-dhcp-config.h"
#include "nm-rfkill-manager.h"
#include "nm-firewall-utils.h"
#include "nm-firewalld-manager.h"
#include "settings/nm-settings-connection.h"
#include "settings/nm-settings.h"
#include "nm-setting-ethtool.h"
#include "nm-setting-ovs-external-ids.h"
#include "nm-setting-user.h"
#include "nm-auth-utils.h"
#include "nm-keep-alive.h"
#include "nm-netns.h"
#include "nm-dispatcher.h"
#include "nm-config.h"
#include "c-list/src/c-list.h"
#include "dns/nm-dns-manager.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "libnm-systemd-core/nm-sd.h"
#include "nm-lldp-listener.h"
#include "nm-audit-manager.h"
#include "nm-connectivity.h"
#include "nm-dbus-interface.h"
#include "nm-hostname-manager.h"
#include "nm-device-generic.h"
#include "nm-device-bridge.h"
#include "nm-device-vlan.h"
#include "nm-device-vrf.h"
#include "nm-device-wireguard.h"
#include "nm-device-logging.h"
/*****************************************************************************/
#define DEFAULT_AUTOCONNECT TRUE
#define GRACE_PERIOD_MULTIPLIER 2U
#define CARRIER_WAIT_TIME_MS 6000
#define CARRIER_WAIT_TIME_AFTER_MTU_MS 10000
#define NM_DEVICE_AUTH_RETRIES_UNSET -1
#define NM_DEVICE_AUTH_RETRIES_INFINITY -2
#define NM_DEVICE_AUTH_RETRIES_DEFAULT 3
/*****************************************************************************/
typedef void (*ActivationHandleFunc)(NMDevice *self);
typedef enum {
CLEANUP_TYPE_KEEP,
CLEANUP_TYPE_REMOVED,
CLEANUP_TYPE_DECONFIGURE,
} CleanupType;
typedef enum _nm_packed {
ADDR_METHOD_STATE_DISABLED,
ADDR_METHOD_STATE_PENDING,
ADDR_METHOD_STATE_GOOD,
ADDR_METHOD_STATE_FAILED,
} AddrMethodState;
typedef struct {
CList lst_slave;
NMDevice *slave;
gulong watch_id;
bool slave_is_enslaved;
bool configure;
} SlaveInfo;
typedef struct {
NMDevice *device;
guint idle_add_id;
} DeleteOnDeactivateData;
typedef struct {
NMDevice *device;
GCancellable *cancellable;
NMPlatformAsyncCallback callback;
gpointer callback_data;
guint num_vfs;
NMOptionBool autoprobe;
} SriovOp;
typedef enum {
/* The various NML3ConfigData types that we track explicitly. Note that
* their relative order matters: higher numbers in this enum means more
* important (and during merge overwrites other settings). This is passed
* as priority to nm_l3cfg_add_config(). */
L3_CONFIG_DATA_TYPE_LL_4,
L3_CONFIG_DATA_TYPE_LL_6,
#define L3_CONFIG_DATA_TYPE_LL_X(IS_IPv4) \
((IS_IPv4) ? L3_CONFIG_DATA_TYPE_LL_4 : L3_CONFIG_DATA_TYPE_LL_6)
L3_CONFIG_DATA_TYPE_AC_6,
L3_CONFIG_DATA_TYPE_PD_6,
L3_CONFIG_DATA_TYPE_DHCP_4,
L3_CONFIG_DATA_TYPE_DHCP_6,
#define L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4) \
((IS_IPv4) ? L3_CONFIG_DATA_TYPE_DHCP_4 : L3_CONFIG_DATA_TYPE_DHCP_6)
L3_CONFIG_DATA_TYPE_SHARED_4,
L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC,
L3_CONFIG_DATA_TYPE_DEVIP_4,
L3_CONFIG_DATA_TYPE_DEVIP_6,
#define L3_CONFIG_DATA_TYPE_DEVIP(addr_family) \
({ \
L3ConfigDataType _t; \
\
switch (addr_family) { \
case AF_INET: \
_t = L3_CONFIG_DATA_TYPE_DEVIP_4; \
break; \
case AF_INET6: \
_t = L3_CONFIG_DATA_TYPE_DEVIP_6; \
break; \
default: \
nm_assert_not_reached(); \
/* fall-through */ \
case AF_UNSPEC: \
_t = L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC; \
break; \
} \
\
_t; \
})
L3_CONFIG_DATA_TYPE_MANUALIP,
_L3_CONFIG_DATA_TYPE_NUM,
_L3_CONFIG_DATA_TYPE_NONE,
_L3_CONFIG_DATA_TYPE_ACD_ONLY,
} L3ConfigDataType;
G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_IPV4LL == L3_CONFIG_DATA_TYPE_LL_4);
G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_IPV6LL == L3_CONFIG_DATA_TYPE_LL_6);
G_STATIC_ASSERT(NM_L3CFG_CONFIG_PRIORITY_VPN == L3_CONFIG_DATA_TYPE_DEVIP_6);
typedef enum {
HW_ADDR_TYPE_UNSET = 0,
HW_ADDR_TYPE_PERMANENT,
HW_ADDR_TYPE_EXPLICIT,
HW_ADDR_TYPE_GENERATED,
} HwAddrType;
typedef enum {
FIREWALL_STATE_UNMANAGED = 0,
FIREWALL_STATE_INITIALIZED,
FIREWALL_STATE_WAIT_STAGE_3,
FIREWALL_STATE_WAIT_IP_CONFIG,
} FirewallState;
typedef struct {
NMIPConfig *ip_config;
} L3IPData;
typedef struct {
GSource *check_async_source;
GSource *req_timeout_source;
union {
const NMDeviceIPState state;
NMDeviceIPState state_;
};
bool wait_for_carrier : 1;
bool wait_for_ports : 1;
bool is_disabled : 1;
bool is_ignore : 1;
bool do_reapply : 1;
} IPStateData;
typedef struct {
NMDhcpClient *client;
NMDhcpConfig *config;
gulong notify_sigid;
NMDeviceIPState state;
union {
struct {
} v4;
struct {
guint needed_prefixes;
NMNDiscDHCPLevel mode;
} v6;
};
} IPDhcpStateData;
typedef struct {
NMDeviceIPState state;
NMDeviceStateReason failed_reason;
} IPDevStateData;
typedef struct {
NMDeviceIPState state;
union {
struct {
NMDnsMasqManager *dnsmasq_manager;
NMNetnsSharedIPHandle *shared_ip_handle;
NMFirewallConfig *firewall_config;
gulong dnsmasq_state_id;
const NML3ConfigData *l3cd;
} v4;
struct {
} v6;
};
} IPSharedStateData;
typedef struct {
NMDeviceIPState state;
union {
struct {
NML3IPv4LL *ipv4ll;
NML3IPv4LLRegistration *ipv4ll_registation;
GSource *timeout_source;
} v4;
struct {
NML3IPv6LL *ipv6ll;
GSource *retry_source;
NML3IPv6LLState llstate;
struct in6_addr lladdr;
} v6;
};
} IPLLStateData;
struct _NMDeviceConnectivityHandle {
CList concheck_lst;
NMDevice *self;
NMDeviceConnectivityCallback callback;
gpointer user_data;
NMConnectivityCheckHandle *c_handle;
guint64 seq;
bool is_periodic : 1;
bool is_periodic_bump : 1;
bool is_periodic_bump_on_complete : 1;
int addr_family;
};
typedef struct {
int ifindex;
NMEthtoolFeatureStates *features;
NMOptionBool requested[_NM_ETHTOOL_ID_FEATURE_NUM];
NMEthtoolCoalesceState *coalesce;
NMEthtoolRingState *ring;
NMEthtoolPauseState *pause;
} EthtoolState;
typedef enum {
RESOLVER_WAIT_ADDRESS = 0,
RESOLVER_IN_PROGRESS,
RESOLVER_DONE,
} ResolverState;
typedef struct {
ResolverState state;
GInetAddress *address;
GCancellable *cancellable;
char *hostname;
NMDevice *device;
guint timeout_id; /* Used when waiting for the address */
int addr_family;
} HostnameResolver;
/*****************************************************************************/
enum {
STATE_CHANGED,
AUTOCONNECT_ALLOWED,
L3CD_CHANGED,
IP6_PREFIX_DELEGATED,
IP6_SUBNET_NEEDED,
REMOVED,
RECHECK_AUTO_ACTIVATE,
RECHECK_ASSUME,
DNS_LOOKUP_DONE,
PLATFORM_ADDRESS_CHANGED,
LAST_SIGNAL,
};
static guint signals[LAST_SIGNAL] = {0};
NM_GOBJECT_PROPERTIES_DEFINE(NMDevice,
PROP_UDI,
PROP_PATH,
PROP_IFACE,
PROP_IP_IFACE,
PROP_DRIVER,
PROP_DRIVER_VERSION,
PROP_FIRMWARE_VERSION,
PROP_CAPABILITIES,
PROP_CARRIER,
PROP_MTU,
PROP_IP4_ADDRESS,
PROP_IP4_CONFIG,
PROP_DHCP4_CONFIG,
#define PROP_DHCPX_CONFIG(IS_IPv4) ((IS_IPv4) ? PROP_DHCP4_CONFIG : PROP_DHCP6_CONFIG)
PROP_IP6_CONFIG,
PROP_DHCP6_CONFIG,
PROP_STATE,
PROP_STATE_REASON,
PROP_ACTIVE_CONNECTION,
PROP_DEVICE_TYPE,
PROP_LINK_TYPE,
PROP_MANAGED,
PROP_AUTOCONNECT,
PROP_FIRMWARE_MISSING,
PROP_NM_PLUGIN_MISSING,
PROP_TYPE_DESC,
PROP_IFINDEX,
PROP_AVAILABLE_CONNECTIONS,
PROP_PHYSICAL_PORT_ID,
PROP_MASTER,
PROP_PARENT,
PROP_HW_ADDRESS,
PROP_PERM_HW_ADDRESS,
PROP_HAS_PENDING_ACTION,
PROP_METERED,
PROP_LLDP_NEIGHBORS,
PROP_REAL,
PROP_SLAVES,
PROP_STATISTICS_REFRESH_RATE_MS,
PROP_STATISTICS_TX_BYTES,
PROP_STATISTICS_RX_BYTES,
PROP_IP4_CONNECTIVITY,
PROP_IP6_CONNECTIVITY,
PROP_INTERFACE_FLAGS,
PROP_PORTS, );
typedef struct _NMDevicePrivate {
guint device_link_changed_id;
guint device_ip_link_changed_id;
NMDeviceState state;
NMDeviceStateReason state_reason;
struct {
guint id;
/* The @state/@reason is only valid, when @id is set. */
NMDeviceState state;
NMDeviceStateReason reason;
} queued_state;
struct {
const char **arr;
guint len;
guint alloc;
} pending_actions;
NMDBusTrackObjPath parent_device;
char *udi;
char *path;
union {
const char *const iface;
char *iface_;
};
union {
const char *const ip_iface;
char *ip_iface_;
};
union {
NML3Cfg *const l3cfg;
NML3Cfg *l3cfg_;
};
union {
struct {
L3IPData l3ipdata_6;
L3IPData l3ipdata_4;
};
L3IPData l3ipdata_x[2];
};
NML3CfgCommitTypeHandle *l3cfg_commit_type;
union {
const int ifindex;
int ifindex_;
};
union {
const int ip_ifindex;
int ip_ifindex_;
};
union {
const NML3ConfigData *d;
} l3cds[_L3_CONFIG_DATA_TYPE_NUM];
int parent_ifindex;
int auth_retries;
union {
struct {
HostnameResolver *hostname_resolver_6;
HostnameResolver *hostname_resolver_4;
};
HostnameResolver *hostname_resolver_x[2];
};
union {
const guint8 hw_addr_len; /* read-only */
guint8 hw_addr_len_;
};
HwAddrType hw_addr_type : 5;
bool real : 1;
NMDeviceType type;
char *type_desc;
NMLinkType link_type;
NMDeviceCapabilities capabilities;
char *driver;
char *driver_version;
char *firmware_version;
bool firmware_missing : 1;
bool nm_plugin_missing : 1;
bool
hw_addr_perm_fake : 1; /* whether the permanent HW address could not be read and is a fake */
guint8 in_state_changed : 4;
NMUtilsStableType current_stable_id_type : 3;
bool activation_state_preserve_external_ports : 1;
bool nm_owned : 1; /* whether the device is a device owned and created by NM */
bool assume_state_guess_assume : 1;
char *assume_state_connection_uuid;
guint64 udi_id;
GHashTable *available_connections;
char *hw_addr;
char *hw_addr_perm;
char *hw_addr_initial;
char *physical_port_id;
guint dev_id;
NMUnmanagedFlags unmanaged_mask;
NMUnmanagedFlags unmanaged_flags;
DeleteOnDeactivateData
*delete_on_deactivate_data; /* data for scheduled cleanup when deleting link (g_idle_add) */
GCancellable *deactivating_cancellable;
NMActRequest *queued_act_request;
bool queued_act_request_is_waiting_for_carrier : 1;
NMDBusTrackObjPath act_request;
GSource *activation_idle_source;
ActivationHandleFunc activation_func;
guint recheck_assume_id;
struct {
guint call_id;
NMDeviceStateReason available_reason;
NMDeviceStateReason unavailable_reason;
} recheck_available;
struct {
NMDispatcherCallId *call_id;
NMDeviceState post_state;
NMDeviceStateReason post_state_reason;
} dispatcher;
/* Link stuff */
guint link_connected_id;
guint link_disconnected_id;
guint carrier_defer_id;
guint carrier_wait_id;
gulong config_changed_id;
gulong ifindex_changed_id;
guint32 mtu;
guint32 ip6_mtu; /* FIXME(l3cfg) */
guint32 mtu_initial;
guint32 ip6_mtu_initial;
NMDeviceMtuSource mtu_source;
guint32 v4_route_table;
guint32 v6_route_table;
/* when carrier goes away, we give a grace period of _get_carrier_wait_ms()
* until taking action.
*
* When changing MTU, the device might take longer then that. So, whenever
* NM changes the MTU it sets @carrier_wait_until_ms to CARRIER_WAIT_TIME_AFTER_MTU_MS
* in the future. This is used to extend the grace period in this particular case. */
gint64 carrier_wait_until_ms;
union {
struct {
NML3ConfigMergeFlags l3config_merge_flags_6;
NML3ConfigMergeFlags l3config_merge_flags_4;
};
NML3ConfigMergeFlags l3config_merge_flags_x[2];
};
union {
const NMDeviceSysIfaceState sys_iface_state;
NMDeviceSysIfaceState sys_iface_state_;
};
bool carrier : 1;
bool ignore_carrier : 1;
bool up : 1; /* IFF_UP */
bool v4_route_table_initialized : 1;
bool v6_route_table_initialized : 1;
bool l3config_merge_flags_has : 1;
bool v4_route_table_all_sync_before : 1;
bool v6_route_table_all_sync_before : 1;
NMDeviceAutoconnectBlockedFlags autoconnect_blocked_flags : 5;
bool is_enslaved : 1;
bool device_link_changed_down : 1;
bool concheck_rp_filter_checked : 1;
bool tc_committed : 1;
NMDeviceStageState stage1_sriov_state : 3;
char *current_stable_id;
NMPacrunnerConfId *pacrunner_conf_id;
struct {
union {
const NMDeviceIPState state;
NMDeviceIPState state_;
};
} ip_data;
union {
struct {
IPStateData ip_data_6;
IPStateData ip_data_4;
};
IPStateData ip_data_x[2];
};
struct {
GSource *carrier_timeout;
union {
struct {
NMDeviceIPState state_6;
NMDeviceIPState state_4;
};
NMDeviceIPState state_x[2];
};
bool carrier_timeout_expired;
} ipmanual_data;
union {
struct {
IPDhcpStateData ipdhcp_data_6;
IPDhcpStateData ipdhcp_data_4;
};
IPDhcpStateData ipdhcp_data_x[2];
};
struct {
NMNDisc *ndisc;
GSource *ndisc_grace_source;
gulong ndisc_changed_id;
gulong ndisc_timeout_id;
NMDeviceIPState state;
const NML3ConfigData *l3cd;
} ipac6_data;
union {
struct {
IPLLStateData ipll_data_6;
IPLLStateData ipll_data_4;
};
IPLLStateData ipll_data_x[2];
};
union {
struct {
IPSharedStateData ipshared_data_6;
IPSharedStateData ipshared_data_4;
};
IPSharedStateData ipshared_data_x[2];
};
union {
struct {
IPDevStateData ipdev_data_6;
IPDevStateData ipdev_data_4;
};
IPDevStateData ipdev_data_x[2];
};
IPDevStateData ipdev_data_unspec;
struct {
/* If we set the addrgenmode6, this records the previously set value. */
guint8 previous_mode_val;
/* whether @previous_mode_val is set. */
bool previous_mode_has : 1;
} addrgenmode6_data;
struct {
NMLogDomain log_domain;
guint timeout;
guint watch;
GPid pid;
char *binary;
char *address;
guint deadline;
} gw_ping;
/* Firewall */
FirewallState fw_state : 4;
NMFirewalldManager *fw_mgr;
NMFirewalldManagerCallId *fw_call;
GHashTable *ip6_saved_properties;
EthtoolState *ethtool_state;
/* master interface for bridge/bond/team slave */
NMDevice *master;
gulong master_ready_id;
int master_ifindex;
/* slave management */
CList slaves; /* list of SlaveInfo */
NMMetered metered;
NMSettings *settings;
NMManager *manager;
NMNetns *netns;
NMLldpListener *lldp_listener;
NMConnectivity *concheck_mgr;
CList concheck_lst_head;
struct {
/* if periodic checks are enabled, this is the source id for the next check. */
guint p_cur_id;
/* the currently configured max periodic interval. */
guint p_max_interval;
/* the current interval. If we are probing, the interval might be lower
* then the configured max interval. */
guint p_cur_interval;
/* the timestamp, when we last scheduled the timer p_cur_id with current interval
* p_cur_interval. */
gint64 p_cur_basetime_ns;
NMConnectivityState state;
} concheck_x[2];
guint check_delete_unrealized_id;
guint32 interface_flags;
struct {
SriovOp *pending; /* SR-IOV operation currently running */
SriovOp *next; /* next SR-IOV operation scheduled */
} sriov;
guint sriov_reset_pending;
struct {
GSource *timeout_source;
guint refresh_rate_ms;
guint64 tx_bytes;
guint64 rx_bytes;
} stats;
bool mtu_force_set_done : 1;
bool needs_ip6_subnet : 1;
NMOptionBool promisc_reset;
GVariant *ports_variant; /* Array of port devices D-Bus path */
char *prop_ip_iface; /* IP interface D-Bus property */
} NMDevicePrivate;
G_DEFINE_ABSTRACT_TYPE(NMDevice, nm_device, NM_TYPE_DBUS_OBJECT)
#define NM_DEVICE_GET_PRIVATE(self) _NM_GET_PRIVATE_PTR(self, NMDevice, NM_IS_DEVICE)
/*****************************************************************************/
static const NMDBusInterfaceInfoExtended interface_info_device;
static const GDBusSignalInfo signal_info_state_changed;
static void _dev_l3_cfg_commit(NMDevice *self, gboolean do_sync);
static void _dev_l3_cfg_commit_type_reset(NMDevice *self);
static gboolean nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure);
static void nm_device_slave_notify_enslave(NMDevice *self, gboolean success);
static void nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason);
static void _dev_ipll6_start(NMDevice *self);
static void _dev_ipac6_start_continue(NMDevice *self);
static void _dev_ipac6_ndisc_set_router_config(NMDevice *self);
static guint32 _dev_default_route_metric_penalty_get(NMDevice *self, int addr_family);
static guint32 _prop_get_ipv4_dad_timeout(NMDevice *self);
static void _carrier_wait_check_queued_act_request(NMDevice *self);
static gint64 _get_carrier_wait_ms(NMDevice *self);
static GBytes *_prop_get_ipv6_dhcp_duid(NMDevice *self,
NMConnection *connection,
GBytes *hwaddr,
gboolean *out_enforce);
static const char *_activation_func_to_string(ActivationHandleFunc func);
static void
_set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting);
static void queued_state_clear(NMDevice *device);
static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data);
static void nm_device_start_ip_check(NMDevice *self);
static void realize_start_setup(NMDevice *self,
const NMPlatformLink *plink,
gboolean assume_state_guess_assume,
const char *assume_state_connection_uuid,
gboolean set_nm_owned,
NMUnmanFlagOp unmanaged_user_explicit,
gboolean force_platform_init);
static void _set_mtu(NMDevice *self, guint32 mtu);
static void _commit_mtu(NMDevice *self);
static void _cancel_activation(NMDevice *self);
static void _dev_ipll4_notify_event(NMDevice *self);
static void _dev_ip_state_check(NMDevice *self, int addr_family);
static void _dev_ipmanual_check_ready(NMDevice *self);
static void
_dev_ipdhcpx_cleanup(NMDevice *self, int addr_family, gboolean reset_dhcp_config, gboolean release);
static void _dev_ip_state_check_async(NMDevice *self, int addr_family);
static void _dev_ipdhcpx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state);
static void _dev_ipdhcpx_restart(NMDevice *self, int addr_family, gboolean release);
static gboolean
_dev_ipac6_grace_period_start(NMDevice *self, guint32 timeout_sec, gboolean force_restart);
static void _dev_ipac6_start(NMDevice *self);
static void _dev_ipac6_set_state(NMDevice *self, NMDeviceIPState state);
static void
_dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, gboolean now);
static void _dev_ipshared4_start(NMDevice *self);
static void _dev_ipshared4_spawn_dnsmasq(NMDevice *self);
static void _dev_ipshared6_start(NMDevice *self);
static void
_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply);
static void concheck_update_state(NMDevice *self,
int addr_family,
NMConnectivityState state,
gboolean is_periodic);
static void sriov_op_cb(GError *error, gpointer user_data);
static void device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self);
static gboolean device_link_changed(gpointer user_data);
static gboolean _get_maybe_ipv6_disabled(NMDevice *self);
/*****************************************************************************/
#define _NMLOG_addr_family(level, prefix, addr_family, fmt, ...) \
G_STMT_START \
{ \
const int _addr_family2 = (addr_family); \
\
_NMLOG(level, \
(_addr_family2 == AF_UNSPEC ? LOGD_IP : LOGD_IPX(NM_IS_IPv4(_addr_family2))), \
"" prefix "%s: " fmt, \
nm_utils_addr_family_to_str(_addr_family2), \
##__VA_ARGS__); \
} \
G_STMT_END
#define _NMLOG_ip(level, ...) _NMLOG_addr_family(level, "ip", __VA_ARGS__)
#define _LOGT_ip(...) _NMLOG_ip(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ip(...) _NMLOG_ip(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ip(...) _NMLOG_ip(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ip(...) _NMLOG_ip(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipll(level, ...) _NMLOG_addr_family(level, "ip:ll", __VA_ARGS__)
#define _LOGT_ipll(...) _NMLOG_ipll(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipll(...) _NMLOG_ipll(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipll(...) _NMLOG_ipll(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipll(...) _NMLOG_ipll(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipdev(level, ...) _NMLOG_addr_family(level, "ip:dev", __VA_ARGS__)
#define _LOGT_ipdev(...) _NMLOG_ipdev(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipdev(...) _NMLOG_ipdev(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipdev(...) _NMLOG_ipdev(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipdev(...) _NMLOG_ipdev(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipdhcp(level, ...) _NMLOG_addr_family(level, "ip:dhcp", __VA_ARGS__)
#define _LOGT_ipdhcp(...) _NMLOG_ipdhcp(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipdhcp(...) _NMLOG_ipdhcp(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipdhcp(...) _NMLOG_ipdhcp(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipdhcp(...) _NMLOG_ipdhcp(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipshared(level, ...) _NMLOG_addr_family(level, "ip:shared", __VA_ARGS__)
#define _LOGT_ipshared(...) _NMLOG_ipshared(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipshared(...) _NMLOG_ipshared(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipshared(...) _NMLOG_ipshared(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipshared(...) _NMLOG_ipshared(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipac6(level, ...) _NMLOG_addr_family(level, "ip:ac6", AF_UNSPEC, __VA_ARGS__)
#define _LOGT_ipac6(...) _NMLOG_ipac6(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipac6(...) _NMLOG_ipac6(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipac6(...) _NMLOG_ipac6(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipac6(...) _NMLOG_ipac6(LOGL_WARN, __VA_ARGS__)
#define _NMLOG_ipmanual(level, ...) _NMLOG_addr_family(level, "ip:manual", __VA_ARGS__)
#define _LOGT_ipmanual(...) _NMLOG_ipmanual(LOGL_TRACE, __VA_ARGS__)
#define _LOGD_ipmanual(...) _NMLOG_ipmanual(LOGL_DEBUG, __VA_ARGS__)
#define _LOGI_ipmanual(...) _NMLOG_ipmanual(LOGL_INFO, __VA_ARGS__)
#define _LOGW_ipmanual(...) _NMLOG_ipmanual(LOGL_WARN, __VA_ARGS__)
/*****************************************************************************/
#define _CACHED_BOOL(cached_value, cmd) \
({ \
NMTernary *const _cached_value = (cached_value); \
\
nm_assert(_cached_value); \
nm_assert_is_ternary(*_cached_value); \
\
if (*_cached_value == NM_TERNARY_DEFAULT) \
*_cached_value = !!(cmd); \
\
!!(*_cached_value); \
})
/*****************************************************************************/
static void
_hostname_resolver_free(HostnameResolver *resolver)
{
if (!resolver)
return;
nm_clear_g_source(&resolver->timeout_id);
nm_clear_g_cancellable(&resolver->cancellable);
nm_g_object_unref(resolver->address);
g_free(resolver->hostname);
nm_g_slice_free(resolver);
}
/*****************************************************************************/
/**
* Update the "ip_iface" property when something changes (device
* state, ifindex) and emit a notify signal if needed. Note that
* the property must be NULL for devices without an ifindex and
* when the device is not activated. This behavior is part of the
* API and should not be changed.
*/
static void
update_prop_ip_iface(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *ip_iface = NULL;
gs_free char *to_free = NULL;
if (nm_device_get_ip_ifindex(self) > 0
&& (priv->state == NM_DEVICE_STATE_UNMANAGED
|| (priv->state >= NM_DEVICE_STATE_IP_CHECK
&& priv->state <= NM_DEVICE_STATE_DEACTIVATING))) {
ip_iface = nm_utils_str_utf8safe_escape(nm_device_get_ip_iface(self),
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL,
&to_free);
}
if (!nm_streq0(priv->prop_ip_iface, ip_iface)) {
g_free(priv->prop_ip_iface);
priv->prop_ip_iface = to_free ? g_steal_pointer(&to_free) : g_strdup(ip_iface);
_notify(self, PROP_IP_IFACE);
}
}
/*****************************************************************************/
static NMSettingIP6ConfigPrivacy
_ip6_privacy_clamp(NMSettingIP6ConfigPrivacy use_tempaddr)
{
switch (use_tempaddr) {
case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
return use_tempaddr;
default:
return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
}
}
/*****************************************************************************/
static const char *
_prop_get_connection_stable_id(NMDevice *self,
NMConnection *connection,
NMUtilsStableType *out_stable_type)
{
NMDevicePrivate *priv;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_CONNECTION(connection));
nm_assert(out_stable_type);
priv = NM_DEVICE_GET_PRIVATE(self);
/* we cache the generated stable ID for the time of an activation.
*
* The reason is, that we don't want the stable-id to change as long
* as the device is active.
*
* Especially with ${RANDOM} stable-id we want to generate *one* configuration
* for each activation. */
if (G_UNLIKELY(!priv->current_stable_id)) {
gs_free char *generated = NULL;
NMUtilsStableType stable_type;
NMSettingConnection *s_con;
gboolean hwaddr_is_fake;
const char *hwaddr;
const char *stable_id;
const char *uuid;
s_con = nm_connection_get_setting_connection(connection);
stable_id = nm_setting_connection_get_stable_id(s_con);
if (!stable_id) {
stable_id =
nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.stable-id"),
self);
}
uuid = nm_connection_get_uuid(connection);
/* the cloned-mac-address may be generated based on the stable-id.
* Thus, at this point, we can only use the permanent MAC address
* as seed. */
hwaddr = nm_device_get_permanent_hw_address_full(self, TRUE, &hwaddr_is_fake);
stable_type = nm_utils_stable_id_parse(stable_id,
nm_device_get_ip_iface(self),
!hwaddr_is_fake ? hwaddr : NULL,
nm_utils_boot_id_str(),
uuid,
&generated);
/* current_stable_id_type is a bitfield! */
priv->current_stable_id_type = stable_type;
nm_assert(stable_type <= (NMUtilsStableType) 0x3);
nm_assert(stable_type + (NMUtilsStableType) 1 > (NMUtilsStableType) 0);
nm_assert(priv->current_stable_id_type == stable_type);
if (stable_type == NM_UTILS_STABLE_TYPE_UUID)
priv->current_stable_id = g_strdup(uuid);
else if (stable_type == NM_UTILS_STABLE_TYPE_STABLE_ID)
priv->current_stable_id = g_strdup(stable_id);
else if (stable_type == NM_UTILS_STABLE_TYPE_GENERATED)
priv->current_stable_id =
nm_str_realloc(nm_utils_stable_id_generated_complete(generated));
else {
nm_assert(stable_type == NM_UTILS_STABLE_TYPE_RANDOM);
priv->current_stable_id = nm_str_realloc(nm_utils_stable_id_random());
}
_LOGT(LOGD_DEVICE,
"stable-id: type=%d, \"%s\""
"%s%s%s",
(int) priv->current_stable_id_type,
priv->current_stable_id,
NM_PRINT_FMT_QUOTED(stable_type == NM_UTILS_STABLE_TYPE_GENERATED,
" from \"",
generated,
"\"",
""));
}
nm_assert(priv->current_stable_id);
*out_stable_type = priv->current_stable_id_type;
return priv->current_stable_id;
}
static GBytes *
_prop_get_ipv6_dhcp_duid(NMDevice *self,
NMConnection *connection,
GBytes *hwaddr,
gboolean *out_enforce)
{
NMSettingIPConfig *s_ip6;
const char *duid;
const char *duid_error;
GBytes *duid_out;
gboolean duid_enforce = TRUE;
gs_free char *logstr1 = NULL;
const guint8 *hwaddr_bin;
gsize hwaddr_len;
int arp_type;
s_ip6 = nm_connection_get_setting_ip6_config(connection);
duid = nm_setting_ip6_config_get_dhcp_duid(NM_SETTING_IP6_CONFIG(s_ip6));
if (!duid) {
duid = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv6.dhcp-duid"),
self);
if (!duid)
duid = "lease";
}
if (nm_streq(duid, "lease")) {
duid_enforce = FALSE;
duid_out = nm_utils_generate_duid_from_machine_id();
goto out_good;
}
if (!_nm_utils_dhcp_duid_valid(duid, &duid_out)) {
duid_error = "invalid duid";
goto out_fail;
}
if (duid_out)
goto out_good;
if (NM_IN_STRSET(duid, "ll", "llt")) {
if (!hwaddr) {
duid_error = "missing link-layer address";
goto out_fail;
}
hwaddr_bin = g_bytes_get_data(hwaddr, &hwaddr_len);
arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len);
if (arp_type < 0) {
duid_error = "unsupported link-layer address";
goto out_fail;
}
if (nm_streq(duid, "ll"))
duid_out = nm_utils_generate_duid_ll(arp_type, hwaddr_bin, hwaddr_len);
else {
duid_out = nm_utils_generate_duid_llt(arp_type,
hwaddr_bin,
hwaddr_len,
nm_utils_host_id_get_timestamp_nsec()
/ NM_UTILS_NSEC_PER_SEC);
}
goto out_good;
}
if (NM_IN_STRSET(duid, "stable-ll", "stable-llt", "stable-uuid")) {
/* preferably, we would salt the checksum differently for each @duid type. We missed
* to do that initially, so most types use the DEFAULT_SALT.
*
* Implementations that are added later, should use a distinct salt instead,
* like "stable-ll"/"stable-llt" with ARPHRD_INFINIBAND below. */
const guint32 DEFAULT_SALT = 670531087u;
nm_auto_free_checksum GChecksum *sum = NULL;
NMUtilsStableType stable_type;
const char *stable_id = NULL;
guint32 salted_header;
const guint8 *host_id;
gsize host_id_len;
union {
guint8 sha256[NM_UTILS_CHECKSUM_LENGTH_SHA256];
guint8 hwaddr_eth[ETH_ALEN];
guint8 hwaddr_infiniband[INFINIBAND_ALEN];
NMUuid uuid;
struct _nm_packed {
guint8 hwaddr[ETH_ALEN];
guint32 timestamp;
} llt_eth;
struct _nm_packed {
guint8 hwaddr[INFINIBAND_ALEN];
guint32 timestamp;
} llt_infiniband;
} digest;
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
if (NM_IN_STRSET(duid, "stable-ll", "stable-llt")) {
/* for stable LL/LLT DUIDs, we still need a hardware address to detect
* the arp-type. Alternatively, we would be able to detect it based on
* other means (e.g. NMDevice type), but instead require the hardware
* address to be present. This is at least consistent with the "ll"/"llt"
* modes above. */
if (!hwaddr) {
duid_error = "missing link-layer address";
goto out_fail;
}
if ((arp_type = nm_utils_arp_type_detect_from_hwaddrlen(g_bytes_get_size(hwaddr)))
< 0) {
duid_error = "unsupported link-layer address";
goto out_fail;
}
if (arp_type == ARPHRD_ETHER)
salted_header = DEFAULT_SALT;
else {
nm_assert(arp_type == ARPHRD_INFINIBAND);
salted_header = 0x42492CEFu + ((guint32) arp_type);
}
} else {
salted_header = DEFAULT_SALT;
arp_type = -1;
}
salted_header = htonl(salted_header + ((guint32) stable_type));
nm_utils_host_id_get(&host_id, &host_id_len);
sum = g_checksum_new(G_CHECKSUM_SHA256);
g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header));
g_checksum_update(sum, (const guchar *) stable_id, -1);
g_checksum_update(sum, (const guchar *) host_id, host_id_len);
nm_utils_checksum_get_digest(sum, digest.sha256);
G_STATIC_ASSERT_EXPR(sizeof(digest) == sizeof(digest.sha256));
if (nm_streq(duid, "stable-ll")) {
switch (arp_type) {
case ARPHRD_ETHER:
duid_out = nm_utils_generate_duid_ll(arp_type,
digest.hwaddr_eth,
sizeof(digest.hwaddr_eth));
break;
case ARPHRD_INFINIBAND:
duid_out = nm_utils_generate_duid_ll(arp_type,
digest.hwaddr_infiniband,
sizeof(digest.hwaddr_infiniband));
break;
default:
g_return_val_if_reached(NULL);
}
} else if (nm_streq(duid, "stable-llt")) {
gint64 time;
guint32 timestamp;
#define EPOCH_DATETIME_THREE_YEARS (356 * 24 * 3600 * 3)
/* We want a variable time between the host_id timestamp and three years
* before. Let's compute the time (in seconds) from 0 to 3 years; then we'll
* subtract it from the host_id timestamp.
*/
time = nm_utils_host_id_get_timestamp_nsec() / NM_UTILS_NSEC_PER_SEC;
/* don't use too old timestamps. They cannot be expressed in DUID-LLT and
* would all be truncated to zero. */
time = NM_MAX(time, NM_UTILS_EPOCH_DATETIME_200001010000 + EPOCH_DATETIME_THREE_YEARS);
switch (arp_type) {
case ARPHRD_ETHER:
timestamp = unaligned_read_be32(&digest.llt_eth.timestamp);
time -= timestamp % EPOCH_DATETIME_THREE_YEARS;
duid_out = nm_utils_generate_duid_llt(arp_type,
digest.llt_eth.hwaddr,
sizeof(digest.llt_eth.hwaddr),
time);
break;
case ARPHRD_INFINIBAND:
timestamp = unaligned_read_be32(&digest.llt_infiniband.timestamp);
time -= timestamp % EPOCH_DATETIME_THREE_YEARS;
duid_out = nm_utils_generate_duid_llt(arp_type,
digest.llt_infiniband.hwaddr,
sizeof(digest.llt_infiniband.hwaddr),
time);
break;
default:
g_return_val_if_reached(NULL);
}
} else {
nm_assert(nm_streq(duid, "stable-uuid"));
duid_out = nm_utils_generate_duid_uuid(&digest.uuid);
}
goto out_good;
}
g_return_val_if_reached(NULL);
out_fail:
nm_assert(!duid_out && duid_error);
{
NMUuid uuid;
_LOGW(LOGD_IP6 | LOGD_DHCP6,
"ipv6.dhcp-duid: failure to generate %s DUID: %s. Fallback to random DUID-UUID.",
duid,
duid_error);
nm_utils_random_bytes(&uuid, sizeof(uuid));
duid_out = nm_utils_generate_duid_uuid(&uuid);
}
out_good:
nm_assert(duid_out);
_LOGD(LOGD_IP6 | LOGD_DHCP6,
"ipv6.dhcp-duid: generate %s DUID '%s' (%s)",
duid,
(logstr1 = nm_dhcp_utils_duid_to_string(duid_out)),
duid_enforce ? "enforcing" : "prefer lease");
NM_SET_OUT(out_enforce, duid_enforce);
return duid_out;
}
static guint32
_prop_get_ipv6_ra_timeout(NMDevice *self)
{
NMConnection *connection;
gint32 timeout;
G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_DEFAULT == 0);
G_STATIC_ASSERT_EXPR(NM_RA_TIMEOUT_INFINITY == G_MAXINT32);
connection = nm_device_get_applied_connection(self);
timeout = nm_setting_ip6_config_get_ra_timeout(
NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection)));
if (timeout > 0)
return timeout;
nm_assert(timeout == 0);
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv6.ra-timeout"),
self,
0,
G_MAXINT32,
0);
}
static NMSettingConnectionMdns
_prop_get_connection_mdns(NMDevice *self)
{
NMConnection *connection;
NMSettingConnectionMdns mdns = NM_SETTING_CONNECTION_MDNS_DEFAULT;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_MDNS_DEFAULT);
connection = nm_device_get_applied_connection(self);
if (connection)
mdns = nm_setting_connection_get_mdns(nm_connection_get_setting_connection(connection));
if (mdns != NM_SETTING_CONNECTION_MDNS_DEFAULT)
return mdns;
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.mdns"),
self,
NM_SETTING_CONNECTION_MDNS_NO,
NM_SETTING_CONNECTION_MDNS_YES,
NM_SETTING_CONNECTION_MDNS_DEFAULT);
}
static NMSettingConnectionLlmnr
_prop_get_connection_llmnr(NMDevice *self)
{
NMConnection *connection;
NMSettingConnectionLlmnr llmnr = NM_SETTING_CONNECTION_LLMNR_DEFAULT;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_LLMNR_DEFAULT);
connection = nm_device_get_applied_connection(self);
if (connection)
llmnr = nm_setting_connection_get_llmnr(nm_connection_get_setting_connection(connection));
if (llmnr != NM_SETTING_CONNECTION_LLMNR_DEFAULT)
return llmnr;
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.llmnr"),
self,
NM_SETTING_CONNECTION_LLMNR_NO,
NM_SETTING_CONNECTION_LLMNR_YES,
NM_SETTING_CONNECTION_LLMNR_DEFAULT);
}
static NMSettingConnectionDnsOverTls
_prop_get_connection_dns_over_tls(NMDevice *self)
{
NMConnection *connection;
NMSettingConnectionDnsOverTls dns_over_tls = NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
connection = nm_device_get_applied_connection(self);
if (connection)
dns_over_tls = nm_setting_connection_get_dns_over_tls(
nm_connection_get_setting_connection(connection));
if (dns_over_tls != NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT)
return dns_over_tls;
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.dns-over-tls"),
self,
NM_SETTING_CONNECTION_DNS_OVER_TLS_NO,
NM_SETTING_CONNECTION_DNS_OVER_TLS_YES,
NM_SETTING_CONNECTION_DNS_OVER_TLS_DEFAULT);
}
static guint32
_prop_get_ipvx_route_table(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass;
NMConnection *connection;
NMSettingIPConfig *s_ip;
guint32 route_table = 0;
gboolean is_user_config = TRUE;
NMSettingConnection *s_con;
NMSettingVrf *s_vrf;
nm_assert_addr_family(addr_family);
/* the route table setting affects how we sync routes. We shall
* not change it while the device is active, hence, cache it. */
if (NM_IS_IPv4(addr_family)) {
if (priv->v4_route_table_initialized)
return priv->v4_route_table;
} else {
if (priv->v6_route_table_initialized)
return priv->v6_route_table;
}
connection = nm_device_get_applied_connection(self);
if (connection) {
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
if (s_ip)
route_table = nm_setting_ip_config_get_route_table(s_ip);
}
if (route_table == 0u) {
gint64 v;
v = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_IS_IPv4(addr_family)
? NM_CON_DEFAULT("ipv4.route-table")
: NM_CON_DEFAULT("ipv6.route-table"),
self,
0,
G_MAXUINT32,
-1);
if (v != -1) {
route_table = v;
is_user_config = FALSE;
}
}
if (route_table == 0u && connection
&& (s_con = nm_connection_get_setting_connection(connection))
&& (nm_streq0(nm_setting_connection_get_slave_type(s_con), NM_SETTING_VRF_SETTING_NAME)
&& priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF)) {
const NMPlatformLnkVrf *lnk;
lnk = nm_platform_link_get_lnk_vrf(nm_device_get_platform(self),
nm_device_get_ifindex(priv->master),
NULL);
if (lnk)
route_table = lnk->table;
}
if (route_table == 0u && connection
&& (s_vrf = (NMSettingVrf *) nm_connection_get_setting(connection, NM_TYPE_SETTING_VRF))) {
route_table = nm_setting_vrf_get_table(s_vrf);
}
klass = NM_DEVICE_GET_CLASS(self);
if (klass->coerce_route_table)
route_table = klass->coerce_route_table(self, addr_family, route_table, is_user_config);
if (NM_IS_IPv4(addr_family)) {
priv->v4_route_table_initialized = TRUE;
priv->v4_route_table = route_table;
} else {
priv->v6_route_table_initialized = TRUE;
priv->v6_route_table = route_table;
}
_LOGT(LOGD_DEVICE,
"ipv%c.route-table = %u%s",
nm_utils_addr_family_to_char(addr_family),
(guint) (route_table ?: RT_TABLE_MAIN),
route_table != 0u ? "" : " (policy routing not enabled)");
return route_table;
}
static gboolean
_prop_get_connection_lldp(NMDevice *self)
{
NMConnection *connection;
NMSettingConnection *s_con;
NMSettingConnectionLldp lldp = NM_SETTING_CONNECTION_LLDP_DEFAULT;
connection = nm_device_get_applied_connection(self);
g_return_val_if_fail(connection, FALSE);
s_con = nm_connection_get_setting_connection(connection);
g_return_val_if_fail(s_con, FALSE);
lldp = nm_setting_connection_get_lldp(s_con);
if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT) {
lldp = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.lldp"),
self,
NM_SETTING_CONNECTION_LLDP_DEFAULT,
NM_SETTING_CONNECTION_LLDP_ENABLE_RX,
NM_SETTING_CONNECTION_LLDP_DEFAULT);
if (lldp == NM_SETTING_CONNECTION_LLDP_DEFAULT)
lldp = NM_SETTING_CONNECTION_LLDP_DISABLE;
}
return lldp == NM_SETTING_CONNECTION_LLDP_ENABLE_RX;
}
static guint32
_prop_get_ipv4_dad_timeout(NMDevice *self)
{
NMConnection *connection;
NMSettingIPConfig *s_ip4 = NULL;
int timeout = -1;
connection = nm_device_get_applied_connection(self);
if (connection)
s_ip4 = nm_connection_get_setting_ip4_config(connection);
if (s_ip4)
timeout = nm_setting_ip_config_get_dad_timeout(s_ip4);
nm_assert(timeout >= -1 && timeout <= NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX);
if (timeout >= 0)
return timeout;
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dad-timeout"),
self,
0,
NM_SETTING_IP_CONFIG_DAD_TIMEOUT_MAX,
0);
}
static guint32
_prop_get_ipvx_dhcp_timeout(NMDevice *self, int addr_family)
{
NMDeviceClass *klass;
NMConnection *connection;
guint32 timeout;
int timeout_i;
nm_assert(NM_IS_DEVICE(self));
nm_assert_addr_family(addr_family);
connection = nm_device_get_applied_connection(self);
timeout_i = nm_setting_ip_config_get_dhcp_timeout(
nm_connection_get_setting_ip_config(connection, addr_family));
nm_assert(timeout_i >= 0 && timeout_i <= G_MAXINT32);
timeout = (guint32) timeout_i;
if (timeout)
goto out;
timeout = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_IS_IPv4(addr_family)
? NM_CON_DEFAULT("ipv4.dhcp-timeout")
: NM_CON_DEFAULT("ipv6.dhcp-timeout"),
self,
0,
G_MAXINT32,
0);
if (timeout)
goto out;
klass = NM_DEVICE_GET_CLASS(self);
if (klass->get_dhcp_timeout_for_device) {
timeout = klass->get_dhcp_timeout_for_device(self, addr_family);
if (timeout)
goto out;
}
timeout = NM_DHCP_TIMEOUT_DEFAULT;
out:
G_STATIC_ASSERT_EXPR(G_MAXINT32 == NM_DHCP_TIMEOUT_INFINITY);
nm_assert(timeout > 0);
nm_assert(timeout <= G_MAXINT32);
return timeout;
}
static guint32
_prop_get_ipvx_dns_priority(NMDevice *self, int addr_family)
{
NMConnection *connection;
NMSettingIPConfig *s_ip;
int prio = 0;
connection = nm_device_get_applied_connection(self);
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
if (s_ip)
prio = nm_setting_ip_config_get_dns_priority(s_ip);
if (prio == 0) {
prio = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.dns-priority")
: NM_CON_DEFAULT("ipv6.dns-priority"),
self,
G_MININT32,
G_MAXINT32,
0);
if (prio == 0) {
prio = nm_device_is_vpn(self) ? NM_DNS_PRIORITY_DEFAULT_VPN
: NM_DNS_PRIORITY_DEFAULT_NORMAL;
}
}
nm_assert(prio != 0);
return prio;
}
static guint32
_prop_get_ipvx_required_timeout(NMDevice *self, int addr_family)
{
NMConnection *connection;
NMSettingIPConfig *s_ip;
int timeout;
nm_assert(NM_IS_DEVICE(self));
nm_assert_addr_family(addr_family);
connection = nm_device_get_applied_connection(self);
if (!connection)
return 0;
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
if (!s_ip)
return 0;
timeout = nm_setting_ip_config_get_required_timeout(s_ip);
nm_assert(timeout >= -1);
if (timeout > -1)
return (guint32) timeout;
return nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.required-timeout")
: NM_CON_DEFAULT("ipv6.required-timeout"),
self,
0,
G_MAXINT32,
0);
}
static gboolean
_prop_get_ipvx_may_fail(NMDevice *self, int addr_family)
{
NMConnection *connection;
NMSettingIPConfig *s_ip = NULL;
connection = nm_device_get_applied_connection(self);
if (connection)
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
return !s_ip || nm_setting_ip_config_get_may_fail(s_ip);
}
static gboolean
_prop_get_ipvx_may_fail_cached(NMDevice *self, int addr_family, NMTernary *cache)
{
return _CACHED_BOOL(cache, _prop_get_ipvx_may_fail(self, addr_family));
}
/**
* _prop_get_ipvx_dhcp_iaid:
* @self: the #NMDevice
* @addr_family: the address family
* @connection: the connection
* @log_silent: whether to log the result.
* @out_is_explicit: on return, %TRUE if the user set a valid IAID in
* the connection or in global configuration; %FALSE if the connection
* property was empty and no valid global configuration was provided.
*
* Returns: a IAID value for this device and the given connection.
*/
static guint32
_prop_get_ipvx_dhcp_iaid(NMDevice *self,
int addr_family,
NMConnection *connection,
gboolean log_silent,
gboolean *out_is_explicit)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMSettingIPConfig *s_ip;
const char *iaid_str;
guint32 iaid;
const char *iface;
const char *fail_reason;
gboolean is_explicit = TRUE;
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
iaid_str = nm_setting_ip_config_get_dhcp_iaid(s_ip);
if (!iaid_str) {
iaid_str = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
IS_IPv4 ? NM_CON_DEFAULT("ipv4.dhcp-iaid")
: NM_CON_DEFAULT("ipv6.dhcp-iaid"),
self);
if (!iaid_str) {
iaid_str = NM_IAID_IFNAME;
is_explicit = FALSE;
} else if (!_nm_utils_iaid_verify(iaid_str, NULL)) {
if (!log_silent) {
_LOGW(LOGD_DEVICE,
"invalid global default '%s' for ipv%c.dhcp-iaid",
iaid_str,
nm_utils_addr_family_to_char(addr_family));
}
iaid_str = NM_IAID_IFNAME;
is_explicit = FALSE;
}
}
if (nm_streq0(iaid_str, NM_IAID_MAC)) {
const NMPlatformLink *pllink;
pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
if (!pllink || pllink->l_address.len < 4) {
fail_reason = "invalid link-layer address";
goto out_fail;
}
/* @iaid is in native endianness. Use unaligned_read_be32()
* so that the IAID for a given MAC address is the same on
* BE and LE machines. */
iaid = unaligned_read_be32(&pllink->l_address.data[pllink->l_address.len - 4]);
goto out_good;
} else if (nm_streq0(iaid_str, NM_IAID_PERM_MAC)) {
guint8 hwaddr_buf[_NM_UTILS_HWADDR_LEN_MAX];
const char *hwaddr_str;
gsize hwaddr_len;
hwaddr_str = nm_device_get_permanent_hw_address(self);
if (!hwaddr_str) {
fail_reason = "no permanent link-layer address";
goto out_fail;
}
if (!_nm_utils_hwaddr_aton(hwaddr_str, hwaddr_buf, sizeof(hwaddr_buf), &hwaddr_len))
g_return_val_if_reached(0);
if (hwaddr_len < 4) {
fail_reason = "invalid link-layer address";
goto out_fail;
}
iaid = unaligned_read_be32(&hwaddr_buf[hwaddr_len - 4]);
goto out_good;
} else if (nm_streq(iaid_str, "stable")) {
nm_auto_free_checksum GChecksum *sum = NULL;
guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
NMUtilsStableType stable_type;
const char *stable_id;
guint32 salted_header;
const guint8 *host_id;
gsize host_id_len;
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
salted_header = htonl(53390459 + stable_type);
nm_utils_host_id_get(&host_id, &host_id_len);
iface = nm_device_get_ip_iface(self);
sum = g_checksum_new(G_CHECKSUM_SHA1);
g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header));
g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1);
g_checksum_update(sum, (const guchar *) iface, strlen(iface) + 1);
g_checksum_update(sum, (const guchar *) host_id, host_id_len);
nm_utils_checksum_get_digest(sum, digest);
iaid = unaligned_read_be32(digest);
goto out_good;
} else if ((iaid = _nm_utils_ascii_str_to_int64(iaid_str, 10, 0, G_MAXUINT32, -1)) != -1) {
goto out_good;
} else {
iface = nm_device_get_ip_iface(self);
iaid = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface));
goto out_good;
}
out_fail:
nm_assert(fail_reason);
if (!log_silent) {
_LOGW(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4),
"ipv%c.dhcp-iaid: failure to generate IAID: %s. Using interface-name based IAID",
nm_utils_addr_family_to_char(addr_family),
fail_reason);
}
is_explicit = FALSE;
iface = nm_device_get_ip_iface(self);
iaid = nm_utils_create_dhcp_iaid(TRUE, (const guint8 *) iface, strlen(iface));
out_good:
if (!log_silent) {
_LOGD(LOGD_DEVICE | LOGD_DHCPX(IS_IPv4) | LOGD_IPX(IS_IPv4),
"ipv%c.dhcp-iaid: using %u (0x%08x) IAID (str: '%s', explicit %d)",
nm_utils_addr_family_to_char(addr_family),
iaid,
iaid,
iaid_str,
is_explicit);
}
NM_SET_OUT(out_is_explicit, is_explicit);
return iaid;
}
static NMDhcpHostnameFlags
_prop_get_ipvx_dhcp_hostname_flags(NMDevice *self, int addr_family)
{
NMConnection *connection;
NMSettingIPConfig *s_ip;
NMDhcpHostnameFlags flags;
gs_free_error GError *error = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DHCP_HOSTNAME_FLAG_NONE);
connection = nm_device_get_applied_connection(self);
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
g_return_val_if_fail(s_ip, NM_DHCP_HOSTNAME_FLAG_NONE);
if (!nm_setting_ip_config_get_dhcp_send_hostname(s_ip))
return NM_DHCP_HOSTNAME_FLAG_NONE;
flags = nm_setting_ip_config_get_dhcp_hostname_flags(s_ip);
if (flags != NM_DHCP_HOSTNAME_FLAG_NONE)
return flags;
flags = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.dhcp-hostname-flags")
: NM_CON_DEFAULT("ipv6.dhcp-hostname-flags"),
self,
0,
NM_DHCP_HOSTNAME_FLAG_FQDN_CLEAR_FLAGS,
0);
if (!_nm_utils_validate_dhcp_hostname_flags(flags, addr_family, &error)) {
_LOGW(LOGD_DEVICE,
"invalid global default value 0x%x for ipv%c.%s: %s",
(guint) flags,
nm_utils_addr_family_to_char(addr_family),
NM_SETTING_IP_CONFIG_DHCP_HOSTNAME_FLAGS,
error->message);
flags = NM_DHCP_HOSTNAME_FLAG_NONE;
}
if (flags != NM_DHCP_HOSTNAME_FLAG_NONE)
return flags;
if (NM_IS_IPv4(addr_family))
return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP4;
else
return NM_DHCP_HOSTNAME_FLAGS_FQDN_DEFAULT_IP6;
}
static const char *
_prop_get_connection_mud_url(NMDevice *self, NMSettingConnection *s_con)
{
const char *mud_url;
const char *s;
mud_url = nm_setting_connection_get_mud_url(s_con);
if (mud_url) {
if (nm_streq(mud_url, NM_CONNECTION_MUD_URL_NONE))
return NULL;
return mud_url;
}
s = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.mud-url"),
self);
if (s) {
if (nm_streq(s, NM_CONNECTION_MUD_URL_NONE))
return NULL;
if (nm_sd_http_url_is_valid_https(s))
return s;
}
return NULL;
}
static GBytes *
_prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *hwaddr)
{
NMSettingIPConfig *s_ip4;
const char *client_id;
guint8 *client_id_buf;
const char *fail_reason;
guint8 hwaddr_bin_buf[_NM_UTILS_HWADDR_LEN_MAX];
const guint8 *hwaddr_bin;
int arp_type;
gsize hwaddr_len;
GBytes *result;
gs_free char *logstr1 = NULL;
s_ip4 = nm_connection_get_setting_ip4_config(connection);
client_id = nm_setting_ip4_config_get_dhcp_client_id(NM_SETTING_IP4_CONFIG(s_ip4));
if (!client_id) {
client_id = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-client-id"),
self);
if (client_id && !client_id[0]) {
/* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes(). */
client_id = NULL;
}
}
if (!client_id) {
_LOGD(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
"ipv4.dhcp-client-id: no explicit client-id configured");
return NULL;
}
if (nm_streq(client_id, "mac")) {
if (!hwaddr) {
fail_reason = "missing link-layer address";
goto out_fail;
}
hwaddr_bin = g_bytes_get_data(hwaddr, &hwaddr_len);
arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len);
if (arp_type < 0) {
fail_reason = "unsupported link-layer address";
goto out_fail;
}
result = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_bin, hwaddr_len);
goto out_good;
}
if (nm_streq(client_id, "perm-mac")) {
const char *hwaddr_str;
hwaddr_str = nm_device_get_permanent_hw_address(self);
if (!hwaddr_str) {
fail_reason = "missing permanent link-layer address";
goto out_fail;
}
if (!_nm_utils_hwaddr_aton(hwaddr_str, hwaddr_bin_buf, sizeof(hwaddr_bin_buf), &hwaddr_len))
g_return_val_if_reached(NULL);
arp_type = nm_utils_arp_type_detect_from_hwaddrlen(hwaddr_len);
if (arp_type < 0) {
fail_reason = "unsupported permanent link-layer address";
goto out_fail;
}
result = nm_utils_dhcp_client_id_mac(arp_type, hwaddr_bin_buf, hwaddr_len);
goto out_good;
}
if (nm_streq(client_id, "duid")) {
guint32 iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, NULL);
result = nm_utils_dhcp_client_id_systemd_node_specific(iaid);
goto out_good;
}
if (nm_streq(client_id, "ipv6-duid")) {
gs_unref_bytes GBytes *duid = NULL;
gboolean iaid_is_explicit;
guint32 iaid;
const guint8 *duid_arr;
gsize duid_len;
iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET, connection, FALSE, &iaid_is_explicit);
if (!iaid_is_explicit)
iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_is_explicit);
duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, NULL);
nm_assert(duid);
duid_arr = g_bytes_get_data(duid, &duid_len);
nm_assert(duid_arr);
nm_assert(duid_len >= 2u + 1u);
nm_assert(duid_len <= 2u + 128u);
result = nm_utils_dhcp_client_id_duid(iaid, duid_arr, duid_len);
goto out_good;
}
if (nm_streq(client_id, "stable")) {
nm_auto_free_checksum GChecksum *sum = NULL;
guint8 digest[NM_UTILS_CHECKSUM_LENGTH_SHA1];
NMUtilsStableType stable_type;
const char *stable_id;
guint32 salted_header;
const guint8 *host_id;
gsize host_id_len;
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
salted_header = htonl(2011610591 + stable_type);
nm_utils_host_id_get(&host_id, &host_id_len);
sum = g_checksum_new(G_CHECKSUM_SHA1);
g_checksum_update(sum, (const guchar *) &salted_header, sizeof(salted_header));
g_checksum_update(sum, (const guchar *) stable_id, strlen(stable_id) + 1);
g_checksum_update(sum, (const guchar *) host_id, host_id_len);
nm_utils_checksum_get_digest(sum, digest);
client_id_buf = g_malloc(1 + 15);
client_id_buf[0] = 0;
memcpy(&client_id_buf[1], digest, 15);
result = g_bytes_new_take(client_id_buf, 1 + 15);
goto out_good;
}
result = nm_dhcp_utils_client_id_string_to_bytes(client_id);
goto out_good;
out_fail:
nm_assert(fail_reason);
_LOGW(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
"ipv4.dhcp-client-id: failure to generate client id (%s). Use random client id",
fail_reason);
client_id_buf = g_malloc(1 + 15);
client_id_buf[0] = 0;
nm_utils_random_bytes(&client_id_buf[1], 15);
result = g_bytes_new_take(client_id_buf, 1 + 15);
out_good:
nm_assert(result);
_LOGD(LOGD_DEVICE | LOGD_DHCP4 | LOGD_IP4,
"ipv4.dhcp-client-id: use \"%s\" client ID: %s",
client_id,
(logstr1 = nm_dhcp_utils_duid_to_string(result)));
return result;
}
static GBytes *
_prop_get_ipv4_dhcp_vendor_class_identifier(NMDevice *self, NMSettingIP4Config *s_ip4)
{
gs_free char *to_free = NULL;
const char *conn_prop;
GBytes *bytes = NULL;
const char *bin;
gsize len;
conn_prop = nm_setting_ip4_config_get_dhcp_vendor_class_identifier(s_ip4);
if (!conn_prop) {
/* set in NetworkManager.conf ? */
conn_prop = nm_config_data_get_connection_default(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-vendor-class-identifier"),
self);
if (conn_prop && !nm_utils_validate_dhcp4_vendor_class_id(conn_prop, NULL))
conn_prop = NULL;
}
if (conn_prop) {
bin = nm_utils_buf_utf8safe_unescape(conn_prop,
NM_UTILS_STR_UTF8_SAFE_FLAG_NONE,
&len,
(gpointer *) &to_free);
if (to_free)
bytes = g_bytes_new_take(g_steal_pointer(&to_free), len);
else
bytes = g_bytes_new(bin, len);
}
return bytes;
}
static NMSettingIP6ConfigPrivacy
_prop_get_ipv6_ip6_privacy(NMDevice *self)
{
NMSettingIP6ConfigPrivacy ip6_privacy;
NMConnection *connection;
g_return_val_if_fail(self, NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
/* 1.) First look at the per-connection setting. If it is not -1 (unknown),
* use it. */
connection = nm_device_get_applied_connection(self);
if (connection) {
NMSettingIPConfig *s_ip6 = nm_connection_get_setting_ip6_config(connection);
if (s_ip6) {
ip6_privacy = nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6));
ip6_privacy = _ip6_privacy_clamp(ip6_privacy);
if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
return ip6_privacy;
}
}
/* 2.) use the default value from the configuration. */
ip6_privacy =
nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv6.ip6-privacy"),
self,
NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN,
NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
if (ip6_privacy != NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN)
return ip6_privacy;
if (!nm_device_get_ip_ifindex(self))
return NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
/* 3.) No valid default-value configured. Fallback to reading sysctl.
*
* Instead of reading static config files in /etc, just read the current sysctl value.
* This works as NM only writes to "/proc/sys/net/ipv6/conf/IFNAME/use_tempaddr", but leaves
* the "default" entry untouched. */
ip6_privacy = nm_platform_sysctl_get_int32(
nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/default/use_tempaddr"),
NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
return _ip6_privacy_clamp(ip6_privacy);
}
static const char *
_prop_get_x_cloned_mac_address(NMDevice *self, NMConnection *connection, gboolean is_wifi)
{
NMSetting *setting;
const char *addr = NULL;
setting = nm_connection_get_setting(connection,
is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
if (setting) {
addr = is_wifi ? nm_setting_wireless_get_cloned_mac_address((NMSettingWireless *) setting)
: nm_setting_wired_get_cloned_mac_address((NMSettingWired *) setting);
}
if (!addr) {
const char *a;
a = nm_config_data_get_connection_default(
NM_CONFIG_GET_DATA,
is_wifi ? NM_CON_DEFAULT("wifi.cloned-mac-address")
: NM_CON_DEFAULT("ethernet.cloned-mac-address"),
self);
addr = NM_CLONED_MAC_PRESERVE;
if (!a) {
if (is_wifi) {
NMSettingMacRandomization v;
/* for backward compatibility, read the deprecated wifi.mac-address-randomization setting. */
v = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("wifi.mac-address-randomization"),
self,
NM_SETTING_MAC_RANDOMIZATION_DEFAULT,
NM_SETTING_MAC_RANDOMIZATION_ALWAYS,
NM_SETTING_MAC_RANDOMIZATION_DEFAULT);
if (v == NM_SETTING_MAC_RANDOMIZATION_ALWAYS)
addr = NM_CLONED_MAC_RANDOM;
}
} else if (NM_CLONED_MAC_IS_SPECIAL(a) || nm_utils_hwaddr_valid(a, ETH_ALEN))
addr = a;
}
return addr;
}
static const char *
_prop_get_x_generate_mac_address_mask(NMDevice *self, NMConnection *connection, gboolean is_wifi)
{
NMSetting *setting;
const char *value;
setting = nm_connection_get_setting(connection,
is_wifi ? NM_TYPE_SETTING_WIRELESS : NM_TYPE_SETTING_WIRED);
if (setting) {
value =
is_wifi
? nm_setting_wireless_get_generate_mac_address_mask((NMSettingWireless *) setting)
: nm_setting_wired_get_generate_mac_address_mask((NMSettingWired *) setting);
if (value)
return value;
}
return nm_config_data_get_connection_default(
NM_CONFIG_GET_DATA,
is_wifi ? NM_CON_DEFAULT("wifi.generate-mac-address-mask")
: NM_CON_DEFAULT("ethernet.generate-mac-address-mask"),
self);
}
/*****************************************************************************/
static void
_ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
{
gs_free NMEthtoolFeatureStates *features = NULL;
features = g_steal_pointer(&ethtool_state->features);
if (!nm_platform_ethtool_set_features(platform,
ethtool_state->ifindex,
features,
ethtool_state->requested,
FALSE))
_LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more offload features");
else
_LOGD(LOGD_DEVICE, "ethtool: offload features successfully reset");
}
static void
_ethtool_features_set(NMDevice *self,
NMPlatform *platform,
EthtoolState *ethtool_state,
NMSettingEthtool *s_ethtool)
{
gs_free NMEthtoolFeatureStates *features = NULL;
if (ethtool_state->features)
_ethtool_features_reset(self, platform, ethtool_state);
if (nm_setting_ethtool_init_features(s_ethtool, ethtool_state->requested) == 0)
return;
features = nm_platform_ethtool_get_link_features(platform, ethtool_state->ifindex);
if (!features) {
_LOGW(LOGD_DEVICE, "ethtool: failure setting offload features (cannot read features)");
return;
}
if (!nm_platform_ethtool_set_features(platform,
ethtool_state->ifindex,
features,
ethtool_state->requested,
TRUE))
_LOGW(LOGD_DEVICE, "ethtool: failure setting one or more offload features");
else
_LOGD(LOGD_DEVICE, "ethtool: offload features successfully set");
ethtool_state->features = g_steal_pointer(&features);
}
static void
_ethtool_coalesce_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
{
gs_free NMEthtoolCoalesceState *coalesce = NULL;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(ethtool_state);
coalesce = g_steal_pointer(&ethtool_state->coalesce);
if (!coalesce)
return;
if (!nm_platform_ethtool_set_coalesce(platform, ethtool_state->ifindex, coalesce))
_LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more coalesce settings");
else
_LOGD(LOGD_DEVICE, "ethtool: coalesce settings successfully reset");
}
static void
_ethtool_coalesce_set(NMDevice *self,
NMPlatform *platform,
EthtoolState *ethtool_state,
NMSettingEthtool *s_ethtool)
{
NMEthtoolCoalesceState coalesce_old;
NMEthtoolCoalesceState coalesce_new;
gboolean has_old = FALSE;
GHashTable *hash;
GHashTableIter iter;
const char *name;
GVariant *variant;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
nm_assert(ethtool_state);
nm_assert(!ethtool_state->coalesce);
hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
if (!hash)
return;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
if (!nm_ethtool_id_is_coalesce(ethtool_id))
continue;
if (!has_old) {
if (!nm_platform_ethtool_get_link_coalesce(platform,
ethtool_state->ifindex,
&coalesce_old)) {
_LOGW(LOGD_DEVICE, "ethtool: failure getting coalesce settings (cannot read)");
return;
}
has_old = TRUE;
coalesce_new = coalesce_old;
}
nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
coalesce_new.s[_NM_ETHTOOL_ID_COALESCE_AS_IDX(ethtool_id)] = g_variant_get_uint32(variant);
}
if (!has_old)
return;
ethtool_state->coalesce = nm_memdup(&coalesce_old, sizeof(coalesce_old));
if (!nm_platform_ethtool_set_coalesce(platform, ethtool_state->ifindex, &coalesce_new)) {
_LOGW(LOGD_DEVICE, "ethtool: failure setting coalesce settings");
return;
}
_LOGD(LOGD_DEVICE, "ethtool: coalesce settings successfully set");
}
static void
_ethtool_ring_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
{
gs_free NMEthtoolRingState *ring = NULL;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(ethtool_state);
ring = g_steal_pointer(&ethtool_state->ring);
if (!ring)
return;
if (!nm_platform_ethtool_set_ring(platform, ethtool_state->ifindex, ring))
_LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more ring settings");
else
_LOGD(LOGD_DEVICE, "ethtool: ring settings successfully reset");
}
static void
_ethtool_ring_set(NMDevice *self,
NMPlatform *platform,
EthtoolState *ethtool_state,
NMSettingEthtool *s_ethtool)
{
NMEthtoolRingState ring_old;
NMEthtoolRingState ring_new;
GHashTable *hash;
GHashTableIter iter;
const char *name;
GVariant *variant;
gboolean has_old = FALSE;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
nm_assert(ethtool_state);
nm_assert(!ethtool_state->ring);
hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
if (!hash)
return;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
guint32 u32;
if (!nm_ethtool_id_is_ring(ethtool_id))
continue;
nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32));
if (!has_old) {
if (!nm_platform_ethtool_get_link_ring(platform, ethtool_state->ifindex, &ring_old)) {
_LOGW(LOGD_DEVICE,
"ethtool: failure setting ring options (cannot read existing setting)");
return;
}
has_old = TRUE;
ring_new = ring_old;
}
u32 = g_variant_get_uint32(variant);
switch (ethtool_id) {
case NM_ETHTOOL_ID_RING_RX:
ring_new.rx_pending = u32;
break;
case NM_ETHTOOL_ID_RING_RX_JUMBO:
ring_new.rx_jumbo_pending = u32;
break;
case NM_ETHTOOL_ID_RING_RX_MINI:
ring_new.rx_mini_pending = u32;
break;
case NM_ETHTOOL_ID_RING_TX:
ring_new.tx_pending = u32;
break;
default:
nm_assert_not_reached();
}
}
if (!has_old)
return;
ethtool_state->ring = nm_memdup(&ring_old, sizeof(ring_old));
if (!nm_platform_ethtool_set_ring(platform, ethtool_state->ifindex, &ring_new)) {
_LOGW(LOGD_DEVICE, "ethtool: failure setting ring settings");
return;
}
_LOGD(LOGD_DEVICE, "ethtool: ring settings successfully set");
}
static void
_ethtool_pause_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
{
gs_free NMEthtoolPauseState *pause = NULL;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(ethtool_state);
pause = g_steal_pointer(&ethtool_state->pause);
if (!pause)
return;
if (!nm_platform_ethtool_set_pause(platform, ethtool_state->ifindex, pause))
_LOGW(LOGD_DEVICE, "ethtool: failure resetting one or more pause settings");
else
_LOGD(LOGD_DEVICE, "ethtool: pause settings successfully reset");
}
static void
_ethtool_pause_set(NMDevice *self,
NMPlatform *platform,
EthtoolState *ethtool_state,
NMSettingEthtool *s_ethtool)
{
NMEthtoolPauseState pause_old;
NMEthtoolPauseState pause_new;
GHashTable *hash;
GHashTableIter iter;
const char *name;
GVariant *variant;
gboolean has_old = FALSE;
NMTernary pause_autoneg = NM_TERNARY_DEFAULT;
NMTernary pause_rx = NM_TERNARY_DEFAULT;
NMTernary pause_tx = NM_TERNARY_DEFAULT;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_PLATFORM(platform));
nm_assert(NM_IS_SETTING_ETHTOOL(s_ethtool));
nm_assert(ethtool_state);
nm_assert(!ethtool_state->pause);
hash = _nm_setting_option_hash(NM_SETTING(s_ethtool), FALSE);
if (!hash)
return;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &name, (gpointer *) &variant)) {
NMEthtoolID ethtool_id = nm_ethtool_id_get_by_name(name);
if (!nm_ethtool_id_is_pause(ethtool_id))
continue;
nm_assert(g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN));
if (!has_old) {
if (!nm_platform_ethtool_get_link_pause(platform, ethtool_state->ifindex, &pause_old)) {
_LOGW(LOGD_DEVICE,
"ethtool: failure setting pause options (cannot read "
"existing setting)");
return;
}
has_old = TRUE;
}
switch (ethtool_id) {
case NM_ETHTOOL_ID_PAUSE_AUTONEG:
pause_autoneg = g_variant_get_boolean(variant);
break;
case NM_ETHTOOL_ID_PAUSE_RX:
pause_rx = g_variant_get_boolean(variant);
break;
case NM_ETHTOOL_ID_PAUSE_TX:
pause_tx = g_variant_get_boolean(variant);
break;
default:
nm_assert_not_reached();
}
}
if (!has_old)
return;
if (pause_rx != NM_TERNARY_DEFAULT || pause_tx != NM_TERNARY_DEFAULT) {
/* this implies to explicitly disable autoneg. */
nm_assert(pause_autoneg != NM_TERNARY_TRUE);
pause_autoneg = NM_TERNARY_FALSE;
}
pause_new = pause_old;
if (pause_autoneg != NM_TERNARY_DEFAULT)
pause_new.autoneg = !!pause_autoneg;
if (pause_rx != NM_TERNARY_DEFAULT)
pause_new.rx = !!pause_rx;
if (pause_tx != NM_TERNARY_DEFAULT)
pause_new.tx = !!pause_tx;
ethtool_state->pause = nm_memdup(&pause_old, sizeof(pause_old));
if (!nm_platform_ethtool_set_pause(platform, ethtool_state->ifindex, &pause_new)) {
_LOGW(LOGD_DEVICE, "ethtool: failure setting pause settings");
return;
}
_LOGD(LOGD_DEVICE, "ethtool: pause settings successfully set");
}
static void
_ethtool_state_reset(NMDevice *self)
{
NMPlatform *platform = nm_device_get_platform(self);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free EthtoolState *ethtool_state = g_steal_pointer(&priv->ethtool_state);
if (!ethtool_state)
return;
if (ethtool_state->features)
_ethtool_features_reset(self, platform, ethtool_state);
if (ethtool_state->coalesce)
_ethtool_coalesce_reset(self, platform, ethtool_state);
if (ethtool_state->ring)
_ethtool_ring_reset(self, platform, ethtool_state);
if (ethtool_state->pause)
_ethtool_pause_reset(self, platform, ethtool_state);
}
static void
_ethtool_state_set(NMDevice *self)
{
int ifindex;
NMPlatform *platform;
NMConnection *connection;
NMSettingEthtool *s_ethtool;
gs_free EthtoolState *ethtool_state = NULL;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
return;
platform = nm_device_get_platform(self);
nm_assert(platform);
connection = nm_device_get_applied_connection(self);
if (!connection)
return;
s_ethtool = NM_SETTING_ETHTOOL(nm_connection_get_setting(connection, NM_TYPE_SETTING_ETHTOOL));
if (!s_ethtool)
return;
ethtool_state = g_new0(EthtoolState, 1);
ethtool_state->ifindex = ifindex;
_ethtool_features_set(self, platform, ethtool_state, s_ethtool);
_ethtool_coalesce_set(self, platform, ethtool_state, s_ethtool);
_ethtool_ring_set(self, platform, ethtool_state, s_ethtool);
_ethtool_pause_set(self, platform, ethtool_state, s_ethtool);
if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring
|| ethtool_state->pause)
priv->ethtool_state = g_steal_pointer(&ethtool_state);
}
/*****************************************************************************/
static gboolean
is_loopback(NMDevice *self)
{
return NM_IS_DEVICE_GENERIC(self) && NM_DEVICE_GET_PRIVATE(self)->ifindex == 1;
}
gboolean
nm_device_is_vpn(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
/* NetworkManager currently treats VPN connections (loaded from NetworkManager VPN plugins)
* differently. Those are considered VPNs.
* However, some native device types may also be considered VPNs...
*
* We should avoid distinguishing between is-vpn and "regular" devices. Is an (unencrypted)
* IP tunnel a VPN? Is MACSec on top of an IP tunnel a VPN?
* Sometimes we differentiate, but avoid unless reasonable. */
return NM_IS_DEVICE_WIREGUARD(self);
}
NMSettings *
nm_device_get_settings(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->settings;
}
NMManager *
nm_device_get_manager(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->manager;
}
NMNetns *
nm_device_get_netns(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->netns;
}
NMDedupMultiIndex *
nm_device_get_multi_index(NMDevice *self)
{
return nm_netns_get_multi_idx(nm_device_get_netns(self));
}
NMPlatform *
nm_device_get_platform(NMDevice *self)
{
return nm_netns_get_platform(nm_device_get_netns(self));
}
static NMConnectivity *
concheck_get_mgr(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (G_UNLIKELY(!priv->concheck_mgr))
priv->concheck_mgr = g_object_ref(nm_connectivity_get());
return priv->concheck_mgr;
}
NML3ConfigData *
nm_device_create_l3_config_data(NMDevice *self, NMIPConfigSource source)
{
int ifindex;
nm_assert(NM_IS_DEVICE(self));
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
g_return_val_if_reached(NULL);
return nm_l3_config_data_new(nm_device_get_multi_index(self), ifindex, source);
}
const NML3ConfigData *
nm_device_create_l3_config_data_from_connection(NMDevice *self, NMConnection *connection)
{
NML3ConfigData *l3cd;
int ifindex;
nm_assert(NM_IS_DEVICE(self));
nm_assert(!connection || NM_IS_CONNECTION(connection));
if (!connection)
return NULL;
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
g_return_val_if_reached(NULL);
l3cd =
nm_l3_config_data_new_from_connection(nm_device_get_multi_index(self), ifindex, connection);
nm_l3_config_data_set_mdns(l3cd, _prop_get_connection_mdns(self));
nm_l3_config_data_set_llmnr(l3cd, _prop_get_connection_llmnr(self));
nm_l3_config_data_set_dns_over_tls(l3cd, _prop_get_connection_dns_over_tls(self));
nm_l3_config_data_set_ip6_privacy(l3cd, _prop_get_ipv6_ip6_privacy(self));
return l3cd;
}
/*****************************************************************************/
NMDeviceSysIfaceState
nm_device_sys_iface_state_get(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
return NM_DEVICE_GET_PRIVATE(self)->sys_iface_state;
}
gboolean
nm_device_sys_iface_state_is_external(NMDevice *self)
{
return NM_IN_SET(nm_device_sys_iface_state_get(self), NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
}
gboolean
nm_device_sys_iface_state_is_external_or_assume(NMDevice *self)
{
return NM_IN_SET(nm_device_sys_iface_state_get(self),
NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
NM_DEVICE_SYS_IFACE_STATE_ASSUME);
}
void
nm_device_sys_iface_state_set(NMDevice *self, NMDeviceSysIfaceState sys_iface_state)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IN_SET(sys_iface_state,
NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
NM_DEVICE_SYS_IFACE_STATE_ASSUME,
NM_DEVICE_SYS_IFACE_STATE_MANAGED,
NM_DEVICE_SYS_IFACE_STATE_REMOVED));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->sys_iface_state != sys_iface_state) {
_LOGT(LOGD_DEVICE,
"sys-iface-state: %s -> %s",
nm_device_sys_iface_state_to_string(priv->sys_iface_state),
nm_device_sys_iface_state_to_string(sys_iface_state));
priv->sys_iface_state_ = sys_iface_state;
_dev_l3_cfg_commit_type_reset(self);
}
/* this function only sets a flag, no immediate actions are initiated.
*
* If you change this, make sure that all callers are fine with such actions. */
nm_assert(priv->sys_iface_state == sys_iface_state);
}
static void
_active_connection_set_state_flags_full(NMDevice *self,
NMActivationStateFlags flags,
NMActivationStateFlags mask)
{
NMActiveConnection *ac;
ac = NM_ACTIVE_CONNECTION(nm_device_get_act_request(self));
if (ac)
nm_active_connection_set_state_flags_full(ac, flags, mask);
}
static void
_active_connection_set_state_flags(NMDevice *self, NMActivationStateFlags flags)
{
_active_connection_set_state_flags_full(self, flags, flags);
}
/*****************************************************************************/
static gboolean
set_interface_flags_full(NMDevice *self,
NMDeviceInterfaceFlags mask,
NMDeviceInterfaceFlags interface_flags,
gboolean notify)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceInterfaceFlags f;
nm_assert(!!mask);
nm_assert(!NM_FLAGS_ANY(mask, ~_NM_DEVICE_INTERFACE_FLAG_ALL));
nm_assert(!NM_FLAGS_ANY(interface_flags, ~mask));
f = (priv->interface_flags & ~mask) | (interface_flags & mask);
if (f == priv->interface_flags)
return FALSE;
priv->interface_flags = f;
if (notify)
_notify(self, PROP_INTERFACE_FLAGS);
return TRUE;
}
static gboolean
set_interface_flags(NMDevice *self,
NMDeviceInterfaceFlags interface_flags,
gboolean set,
gboolean notify)
{
return set_interface_flags_full(self,
interface_flags,
set ? interface_flags : NM_DEVICE_INTERFACE_FLAG_NONE,
notify);
}
void
nm_device_assume_state_get(NMDevice *self,
gboolean *out_assume_state_guess_assume,
const char **out_assume_state_connection_uuid)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
NM_SET_OUT(out_assume_state_guess_assume, priv->assume_state_guess_assume);
NM_SET_OUT(out_assume_state_connection_uuid, priv->assume_state_connection_uuid);
}
static void
_assume_state_set(NMDevice *self,
gboolean assume_state_guess_assume,
const char *assume_state_connection_uuid)
{
NMDevicePrivate *priv;
nm_assert(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->assume_state_guess_assume == !!assume_state_guess_assume
&& nm_streq0(priv->assume_state_connection_uuid, assume_state_connection_uuid))
return;
_LOGD(LOGD_DEVICE,
"assume-state: set guess-assume=%c, connection=%s%s%s",
assume_state_guess_assume ? '1' : '0',
NM_PRINT_FMT_QUOTE_STRING(assume_state_connection_uuid));
priv->assume_state_guess_assume = assume_state_guess_assume;
g_free(priv->assume_state_connection_uuid);
priv->assume_state_connection_uuid = g_strdup(assume_state_connection_uuid);
}
void
nm_device_assume_state_reset(NMDevice *self)
{
g_return_if_fail(NM_IS_DEVICE(self));
_assume_state_set(self, FALSE, NULL);
}
/*****************************************************************************/
static char *
nm_device_sysctl_ip_conf_get(NMDevice *self, int addr_family, const char *property)
{
const char *ifname;
nm_assert_addr_family(addr_family);
ifname = nm_device_get_ip_iface_from_platform(self);
if (!ifname)
return NULL;
return nm_platform_sysctl_ip_conf_get(nm_device_get_platform(self),
addr_family,
ifname,
property);
}
static gint64
nm_device_sysctl_ip_conf_get_int_checked(NMDevice *self,
int addr_family,
const char *property,
guint base,
gint64 min,
gint64 max,
gint64 fallback)
{
const char *ifname;
nm_assert_addr_family(addr_family);
ifname = nm_device_get_ip_iface_from_platform(self);
if (!ifname) {
errno = EINVAL;
return fallback;
}
return nm_platform_sysctl_ip_conf_get_int_checked(nm_device_get_platform(self),
addr_family,
ifname,
property,
base,
min,
max,
fallback);
}
gboolean
nm_device_sysctl_ip_conf_set(NMDevice *self,
int addr_family,
const char *property,
const char *value)
{
NMPlatform *platform = nm_device_get_platform(self);
gs_free char *value_to_free = NULL;
const char *ifname;
nm_assert_addr_family(addr_family);
ifname = nm_device_get_ip_iface_from_platform(self);
if (!ifname)
return FALSE;
if (!value) {
/* Set to a default value when we've got a NULL @value. */
value_to_free = nm_platform_sysctl_ip_conf_get(platform, addr_family, "default", property);
value = value_to_free;
if (!value)
return FALSE;
}
return nm_platform_sysctl_ip_conf_set(platform, addr_family, ifname, property, value);
}
/*****************************************************************************/
gboolean
nm_device_has_capability(NMDevice *self, NMDeviceCapabilities caps)
{
return NM_FLAGS_ANY(NM_DEVICE_GET_PRIVATE(self)->capabilities, caps);
}
static void
_add_capabilities(NMDevice *self, NMDeviceCapabilities capabilities)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!NM_FLAGS_ALL(priv->capabilities, capabilities)) {
priv->capabilities |= capabilities;
_notify(self, PROP_CAPABILITIES);
}
}
/*****************************************************************************/
static void
_dev_ip_state_req_timeout_cancel(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (addr_family == AF_UNSPEC) {
_dev_ip_state_req_timeout_cancel(self, AF_INET);
_dev_ip_state_req_timeout_cancel(self, AF_INET6);
return;
}
if (nm_clear_g_source_inst(&priv->ip_data_x[NM_IS_IPv4(addr_family)].req_timeout_source))
_LOGD_ip(addr_family, "required-timeout: cancelled");
}
static gboolean
_dev_ip_state_req_timeout_cb_x(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_LOGD_ip(addr_family, "required-timeout: expired");
nm_clear_g_source_inst(&priv->ip_data_x[NM_IS_IPv4(addr_family)].req_timeout_source);
_dev_ip_state_check(self, nm_utils_addr_family_other(addr_family));
return G_SOURCE_CONTINUE;
}
static gboolean
_dev_ip_state_req_timeout_cb_4(gpointer user_data)
{
return _dev_ip_state_req_timeout_cb_x(user_data, AF_INET);
}
static gboolean
_dev_ip_state_req_timeout_cb_6(gpointer user_data)
{
return _dev_ip_state_req_timeout_cb_x(user_data, AF_INET6);
}
static void
_dev_ip_state_req_timeout_schedule(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
guint32 timeout_msec;
char buf[32];
nm_assert(!priv->ip_data_x[IS_IPv4].req_timeout_source);
timeout_msec = _prop_get_ipvx_required_timeout(self, addr_family);
if (timeout_msec == 0) {
_LOGD_ip(addr_family, "required-timeout: disabled");
return;
}
_LOGD_ip(addr_family,
"required-timeout: started (%s msec)",
timeout_msec == G_MAXINT32 ? "" : nm_sprintf_buf(buf, "%u", timeout_msec));
if (timeout_msec == G_MAXINT32) {
priv->ip_data_x[IS_IPv4].req_timeout_source = g_source_ref(nm_g_source_sentinel_get(0));
} else {
priv->ip_data_x[IS_IPv4].req_timeout_source = nm_g_timeout_add_source(
timeout_msec,
IS_IPv4 ? _dev_ip_state_req_timeout_cb_4 : _dev_ip_state_req_timeout_cb_6,
self);
}
}
static gboolean
_dev_ip_state_set_state(NMDevice *self,
int addr_family,
NMDeviceIPState ip_state,
const char *reason)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int IS_IPv4;
if (addr_family == AF_UNSPEC) {
if (priv->ip_data.state == ip_state)
return FALSE;
_LOGD_ip(addr_family,
"set (combined) state %s (was %s, reason: %s)",
nm_device_ip_state_to_string(ip_state),
nm_device_ip_state_to_string(priv->ip_data.state),
reason);
priv->ip_data.state_ = ip_state;
return TRUE;
}
IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ip_data_x[IS_IPv4].state_ == ip_state)
return FALSE;
_LOGD_ip(addr_family,
"set state %s (was %s, reason: %s)",
nm_device_ip_state_to_string(ip_state),
nm_device_ip_state_to_string(priv->ip_data_x[IS_IPv4].state),
reason);
priv->ip_data_x[IS_IPv4].state_ = ip_state;
return TRUE;
}
static void
_device_ip_state_accumulate(NMDeviceIPState state,
gboolean *out_is_started,
gboolean *out_is_pending,
gboolean *out_is_failed)
{
switch (state) {
case NM_DEVICE_IP_STATE_NONE:
return;
case NM_DEVICE_IP_STATE_PENDING:
*out_is_started = TRUE;
*out_is_pending = TRUE;
return;
case NM_DEVICE_IP_STATE_READY:
*out_is_started = TRUE;
return;
case NM_DEVICE_IP_STATE_FAILED:
*out_is_started = TRUE;
*out_is_failed = TRUE;
return;
}
nm_assert_not_reached();
return;
}
static void
_dev_ip_state_check(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
gboolean s_is_started = FALSE;
gboolean s_is_failed = FALSE;
gboolean s_is_pending = FALSE;
gboolean has_tna = FALSE;
gboolean v_bool;
NMDeviceIPState ip_state;
NMDeviceIPState ip_state_other;
NMDeviceIPState combinedip_state;
NMTernary may_fail = NM_TERNARY_DEFAULT;
NMTernary may_fail_other = NM_TERNARY_DEFAULT;
gboolean disabled_or_ignore = FALSE;
gboolean disabled_or_ignore_other = FALSE;
if (priv->ip_data_x[IS_IPv4].is_disabled || priv->ip_data_x[IS_IPv4].is_ignore)
disabled_or_ignore = TRUE;
if (priv->ip_data_x[!IS_IPv4].is_disabled || priv->ip_data_x[!IS_IPv4].is_ignore)
disabled_or_ignore_other = TRUE;
/* State handling in NMDevice:
*
* NMDevice manages a lot of state, that is for the various IP addressing methods, the state
* of the interface (controller/port), and a overall nm_device_get_state().
*
* The idea is to compartmentalize these states into smaller units, and combine them as appropriate.
*
* For example, NMDhcpClient already provides an API that hides most of the complexity. But it still
* needs to expose some state, like whether we are still trying to get a lease (PENDING), whether there
* was a critical failure (FAILED) or we have a lease (READY). This state is grouped in NMDevice
* under priv->ipdhcp_data_x. Most important is priv->ipdhcp_data_x[].state, which distills all of this
* into 4 values of type NMDeviceIPState. This state is cached, so whenever something changes (e.g.
* an event from NMDhcpClient), we determine the new state and compare it with what is cached. If
* the cached state is as the new state, we are done. Otherwise, the change gets escalated (which
* means to call _dev_ip_state_check_async()).
*
* Then, the various sub-states escalate their changes to this function (_dev_ip_state_check). This
* function first takes the sub-states related to one IP address family, and combines them into
* priv->ip_data_x[] (and in particular priv->ip_data_x[].state). The same repeats. The current
* state is cached in priv->ip_data_x[].state, and _dev_ip_state_check() determines the new state.
* If there is no change, it ends here. Otherwise, it gets escalated. In this case, the escaplation
* happens in _dev_ip_state_check() below by combining the combined per-address-family into
* priv->ip_data. In particular this step needs to take into account settings like "may-fail"
* and "required-timeout".
*
* The escalation and compartmentalization priv->ip_data repeats. This time it escalates
* to the overall device state (nm_device_state_changed() and nm_device_get_state()), which then
* triggers larger state changes (e.g. the activation might fail).
*/
if (priv->l3cfg && nm_l3cfg_commit_on_idle_is_scheduled(priv->l3cfg)) {
/* we have an update on NML3Cfg scheduled. We first process that, before
* progressing the IP state. When that's done, we will be called again. */
_dev_ip_state_check_async(self, addr_family);
return;
}
if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_NONE) {
ip_state = NM_DEVICE_IP_STATE_NONE;
goto got_ip_state;
}
if (nm_device_sys_iface_state_is_external(self)) {
ip_state = NM_DEVICE_IP_STATE_READY;
goto got_ip_state;
}
if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_PENDING
&& (priv->state < NM_DEVICE_STATE_IP_CONFIG || priv->state > NM_DEVICE_STATE_ACTIVATED)) {
/* we can only leave pending state, if we are between (including) IP_CONFIG and ACTIVATED states. */
ip_state = NM_DEVICE_IP_STATE_PENDING;
goto got_ip_state;
}
if (priv->ip_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_PENDING
&& nm_active_connection_get_master(NM_ACTIVE_CONNECTION(priv->act_request.obj))
&& !priv->is_enslaved) {
/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
* master to enslave us. */
ip_state = NM_DEVICE_IP_STATE_PENDING;
goto got_ip_state;
}
if (priv->ip_data_x[IS_IPv4].wait_for_carrier || priv->ip_data_x[IS_IPv4].wait_for_ports) {
ip_state = NM_DEVICE_IP_STATE_PENDING;
goto got_ip_state;
}
if (disabled_or_ignore) {
ip_state = NM_DEVICE_IP_STATE_READY;
goto got_ip_state;
}
_device_ip_state_accumulate(priv->ipmanual_data.state_x[IS_IPv4],
&s_is_started,
&s_is_pending,
&s_is_failed);
_device_ip_state_accumulate(priv->ipll_data_x[IS_IPv4].state,
&s_is_started,
&s_is_pending,
&s_is_failed);
if (!IS_IPv4) {
_device_ip_state_accumulate(priv->ipac6_data.state,
&s_is_started,
&s_is_pending,
&s_is_failed);
}
v_bool = FALSE;
_device_ip_state_accumulate(priv->ipdhcp_data_x[IS_IPv4].state,
&s_is_started,
&s_is_pending,
&v_bool);
if (v_bool) {
if (!IS_IPv4 && priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) {
/* DHCPv6 is best-effort and not required. */
} else
s_is_failed = TRUE;
}
_device_ip_state_accumulate(priv->ipshared_data_x[IS_IPv4].state,
&s_is_started,
&s_is_pending,
&s_is_failed);
_device_ip_state_accumulate(priv->ipdev_data_x[IS_IPv4].state,
&s_is_started,
&s_is_pending,
&s_is_failed);
_device_ip_state_accumulate(priv->ipdev_data_unspec.state,
&s_is_started,
&s_is_pending,
&s_is_failed);
has_tna = priv->l3cfg && nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family);
if (has_tna)
s_is_pending = TRUE;
if (s_is_failed)
ip_state = NM_DEVICE_IP_STATE_FAILED;
else if (s_is_pending)
ip_state = NM_DEVICE_IP_STATE_PENDING;
else if (s_is_started)
ip_state = NM_DEVICE_IP_STATE_READY;
else
ip_state = NM_DEVICE_IP_STATE_PENDING;
got_ip_state:
#define _state_str_a(state, name) \
({ \
const NMDeviceIPState _state = (state); \
char *_s = ""; \
\
if (_state != NM_DEVICE_IP_STATE_NONE) { \
_s = nm_sprintf_bufa(NM_STRLEN(name) + 11, \
" " name "=%s", \
nm_device_ip_state_to_string(_state)); \
} \
_s; \
})
nm_assert(!priv->ip_data_4.is_ignore);
_LOGT_ip(addr_family,
"check-state: state %s => %s, is_failed=%d, is_pending=%d, is_started=%d temp_na=%d, "
"may-fail-4=%d, may-fail-6=%d;"
"%s;%s%s%s%s%s%s;%s%s%s%s%s%s%s%s",
nm_device_ip_state_to_string(priv->ip_data_x[IS_IPv4].state),
nm_device_ip_state_to_string(ip_state),
s_is_failed,
s_is_pending,
s_is_started,
has_tna,
_prop_get_ipvx_may_fail_cached(self, AF_INET, IS_IPv4 ? &may_fail : &may_fail_other),
_prop_get_ipvx_may_fail_cached(self, AF_INET6, !IS_IPv4 ? &may_fail : &may_fail_other),
priv->ip_data_4.is_disabled ? " disabled4" : "",
_state_str_a(priv->ipmanual_data.state_4, "manualip4"),
_state_str_a(priv->ipdev_data_unspec.state, "dev"),
_state_str_a(priv->ipll_data_4.state, "ll4"),
_state_str_a(priv->ipdhcp_data_4.state, "dhcp4"),
_state_str_a(priv->ipdev_data_4.state, "dev4"),
_state_str_a(priv->ipshared_data_4.state, "shared4"),
priv->ip_data_6.is_disabled ? " disabled6" : "",
priv->ip_data_6.is_ignore ? " ignore6" : "",
_state_str_a(priv->ipmanual_data.state_6, "manualip6"),
_state_str_a(priv->ipll_data_6.state, "ll6"),
_state_str_a(priv->ipac6_data.state, "ac6"),
_state_str_a(priv->ipdhcp_data_6.state, "dhcp6"),
_state_str_a(priv->ipdev_data_6.state, "dev6"),
_state_str_a(priv->ipshared_data_6.state, "shared6"));
if (priv->ip_data_x[IS_IPv4].state == ip_state) {
/* no change. We can stop here. However, we also cancel the pending check, if any,
* because we just determined that there is no change. */
} else {
_dev_ip_state_set_state(self, addr_family, ip_state, "check-ip-state");
}
if (ip_state == NM_DEVICE_IP_STATE_NONE) {
/* Nothing to do. This almost cannot happen, and there is probably nothing
* to do about this case. */
goto out_done;
}
ip_state_other = priv->ip_data_x[!IS_IPv4].state;
if (ip_state == NM_DEVICE_IP_STATE_READY) {
/* we only set NM_ACTIVATION_STATE_FLAG_IP_READY_X() flag once we reach NM_DEVICE_IP_STATE_READY state.
* We don't ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAILED state.
*
* This is not documented/guaranteed behavior, but seems to make sense for now. */
_active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_IP_READY_X(IS_IPv4));
}
if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_READY)
combinedip_state = NM_DEVICE_IP_STATE_READY;
else if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_PENDING
&& disabled_or_ignore) {
/* This IP method is disabled/ignore, but the other family is still pending.
* Regardless of ipvx.may-fail, this means that we always require the other IP family
* to get ready too. */
combinedip_state = NM_DEVICE_IP_STATE_PENDING;
} else if (ip_state == NM_DEVICE_IP_STATE_READY && ip_state_other == NM_DEVICE_IP_STATE_PENDING
&& (priv->ip_data_x[!IS_IPv4].req_timeout_source
|| !_prop_get_ipvx_may_fail_cached(self,
nm_utils_addr_family_other(addr_family),
&may_fail_other)))
combinedip_state = NM_DEVICE_IP_STATE_PENDING;
else if (ip_state == NM_DEVICE_IP_STATE_READY
&& _prop_get_ipvx_may_fail_cached(self,
nm_utils_addr_family_other(addr_family),
&may_fail_other))
combinedip_state = NM_DEVICE_IP_STATE_READY;
else if (ip_state == NM_DEVICE_IP_STATE_FAILED
&& !_prop_get_ipvx_may_fail_cached(self, addr_family, &may_fail))
combinedip_state = NM_DEVICE_IP_STATE_FAILED;
else if ((ip_state == NM_DEVICE_IP_STATE_FAILED
|| (ip_state == NM_DEVICE_IP_STATE_READY && disabled_or_ignore))
&& (ip_state_other == NM_DEVICE_IP_STATE_FAILED
|| (ip_state_other == NM_DEVICE_IP_STATE_READY && disabled_or_ignore_other))) {
/* If both IP states failed, or one failed and the other is disabled
* then it's a failure. may-fail does not mean that both families may
* fail, instead it means that at least one family must succeed. */
if (nm_device_sys_iface_state_is_external_or_assume(self)) {
_dev_ip_state_set_state(self, AF_INET, NM_DEVICE_IP_STATE_READY, "assumed");
_dev_ip_state_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_READY, "assumed");
combinedip_state = NM_DEVICE_IP_STATE_READY;
} else {
combinedip_state = NM_DEVICE_IP_STATE_FAILED;
}
} else {
if (priv->ip_data.state == NM_DEVICE_IP_STATE_NONE)
combinedip_state = NM_DEVICE_IP_STATE_PENDING;
else
combinedip_state = priv->ip_data.state;
}
_LOGT_ip(AF_UNSPEC,
"check-state: (combined) state %s => %s",
nm_device_ip_state_to_string(priv->ip_data.state),
nm_device_ip_state_to_string(combinedip_state));
if (!_dev_ip_state_set_state(self, AF_UNSPEC, combinedip_state, "check-ip-state"))
goto out_done;
switch (combinedip_state) {
case NM_DEVICE_IP_STATE_PENDING:
break;
case NM_DEVICE_IP_STATE_READY:
_dev_ip_state_req_timeout_cancel(self, AF_UNSPEC);
if (priv->state == NM_DEVICE_STATE_IP_CONFIG) {
nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
}
break;
case NM_DEVICE_IP_STATE_FAILED:
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
break;
case NM_DEVICE_IP_STATE_NONE:
default:
nm_assert_not_reached();
}
out_done:
/* we just checked the state. We can cancel the pending async check. */
nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].check_async_source);
}
static gboolean
_dev_ip_state_check_async_cb(NMDevice *self, int addr_family)
{
_dev_ip_state_check(self, addr_family);
return G_SOURCE_CONTINUE;
}
static gboolean
_dev_ip_state_check_async_cb_4(gpointer user_data)
{
return _dev_ip_state_check_async_cb(user_data, AF_INET);
}
static gboolean
_dev_ip_state_check_async_cb_6(gpointer user_data)
{
return _dev_ip_state_check_async_cb(user_data, AF_INET6);
}
static void
_dev_ip_state_check_async(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int IS_IPv4;
if (addr_family == AF_UNSPEC) {
_dev_ip_state_check_async(self, AF_INET);
_dev_ip_state_check_async(self, AF_INET6);
return;
}
IS_IPv4 = NM_IS_IPv4(addr_family);
if (!priv->ip_data_x[IS_IPv4].check_async_source) {
priv->ip_data_x[IS_IPv4].check_async_source = nm_g_idle_add_source(
(IS_IPv4 ? _dev_ip_state_check_async_cb_4 : _dev_ip_state_check_async_cb_6),
self);
}
}
static void
_dev_ip_state_cleanup(NMDevice *self, int addr_family, gboolean from_reapply)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int IS_IPv4;
if (addr_family == AF_UNSPEC) {
_dev_ip_state_set_state(self,
addr_family,
from_reapply ? NM_DEVICE_IP_STATE_PENDING : NM_DEVICE_IP_STATE_NONE,
"ip-state-clear");
return;
}
IS_IPv4 = NM_IS_IPv4(addr_family);
nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].check_async_source);
nm_clear_g_source_inst(&priv->ip_data_x[IS_IPv4].req_timeout_source);
_dev_ip_state_set_state(self,
addr_family,
from_reapply ? NM_DEVICE_IP_STATE_PENDING : NM_DEVICE_IP_STATE_NONE,
"ip-state-clear");
priv->ip_data_x[IS_IPv4].wait_for_carrier = FALSE;
priv->ip_data_x[IS_IPv4].wait_for_ports = FALSE;
priv->ip_data_x[IS_IPv4].is_disabled = FALSE;
priv->ip_data_x[IS_IPv4].is_ignore = FALSE;
priv->ip_data_x[IS_IPv4].do_reapply = FALSE;
}
/*****************************************************************************/
static gpointer
_dev_l3_config_data_tag_get(NMDevicePrivate *priv, L3ConfigDataType l3cd_type)
{
nm_assert(_NM_INT_NOT_NEGATIVE(l3cd_type) && l3cd_type < G_N_ELEMENTS(priv->l3cds));
return &priv->l3cds[l3cd_type];
}
static L3ConfigDataType
_dev_l3_config_data_tag_to_type(NMDevice *self, gconstpointer tag)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int d;
/* In C it is undefined behavior to compare unrelated pointers.
* Work around that by using nm_ptr_to_uintptr(), which casts the pointers
* to integers.
*
* I guess, theoretically it's still a problem to assume that if tag pointers
* somewhere inside priv->l3cds, that the uintptr_t case would also yield
* a value in that range. In practice, I couldn't imaging this not not work
* reliably. */
if (nm_ptr_to_uintptr(tag) < nm_ptr_to_uintptr(&priv->l3cds[0])
|| nm_ptr_to_uintptr(tag) >= nm_ptr_to_uintptr(&priv->l3cds[G_N_ELEMENTS(priv->l3cds)]))
return _L3_CONFIG_DATA_TYPE_NONE;
d = ((typeof(priv->l3cds[0]) *) tag) - (&priv->l3cds[0]);
nm_assert(d >= 0);
nm_assert(d < _L3_CONFIG_DATA_TYPE_NUM);
nm_assert(tag == &priv->l3cds[d]);
nm_assert(tag == _dev_l3_config_data_tag_get(priv, d));
return d;
}
static L3ConfigDataType
_dev_l3_config_data_acd_addr_info_to_type(NMDevice *self,
const NML3AcdAddrInfo *addr_info,
guint i_track_infos)
{
nm_assert(NM_IS_DEVICE(self));
nm_assert(addr_info);
nm_assert(i_track_infos < addr_info->n_track_infos);
return _dev_l3_config_data_tag_to_type(self, addr_info->track_infos[i_track_infos].tag);
}
// FIXME(l3cfg): unused function??
_nm_unused static const NML3AcdAddrTrackInfo *
_dev_l3_config_data_acd_addr_info_has_by_type(NMDevice *self,
const NML3AcdAddrInfo *addr_info,
L3ConfigDataType l3cd_type)
{
guint i;
nm_assert(NM_IS_DEVICE(self));
nm_assert(addr_info);
nm_assert(_NM_INT_NOT_NEGATIVE(l3cd_type) && l3cd_type < _L3_CONFIG_DATA_TYPE_NUM);
for (i = 0; i < addr_info->n_track_infos; i++) {
if (l3cd_type == _dev_l3_config_data_acd_addr_info_to_type(self, addr_info, i))
return &addr_info->track_infos[i];
}
return NULL;
}
static void
_dev_l3_get_config_settings(NMDevice *self,
L3ConfigDataType type,
NML3ConfigMergeFlags *out_merge_flags,
NML3AcdDefendType *out_acd_defend_type,
guint32 *out_acd_timeout_msec)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NML3ConfigMergeFlags flags;
NMConnection *connection;
NMSettingIPConfig *s_ip;
nm_assert(_NM_INT_NOT_NEGATIVE(type) && type < _L3_CONFIG_DATA_TYPE_NUM);
if (G_UNLIKELY(!priv->l3config_merge_flags_has)) {
int IS_IPv4;
connection = nm_device_get_applied_connection(self);
for (IS_IPv4 = 0; IS_IPv4 < 2; IS_IPv4++) {
flags = NM_L3_CONFIG_MERGE_FLAGS_NONE;
if (connection
&& (s_ip = nm_connection_get_setting_ip_config(connection,
IS_IPv4 ? AF_INET : AF_INET6))) {
if (nm_setting_ip_config_get_ignore_auto_routes(s_ip))
flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_ROUTES;
if (nm_setting_ip_config_get_ignore_auto_dns(s_ip))
flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DNS;
if (nm_setting_ip_config_get_never_default(s_ip)
|| nm_setting_ip_config_get_gateway(s_ip)) {
/* if the connection has an explicit gateway, we also ignore
* the default routes from other sources. */
flags |= NM_L3_CONFIG_MERGE_FLAGS_NO_DEFAULT_ROUTES;
}
}
priv->l3config_merge_flags_x[IS_IPv4] = flags;
}
priv->l3config_merge_flags_has = TRUE;
}
switch (type) {
case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
case L3_CONFIG_DATA_TYPE_MANUALIP:
case L3_CONFIG_DATA_TYPE_LL_4:
case L3_CONFIG_DATA_TYPE_LL_6:
case L3_CONFIG_DATA_TYPE_PD_6:
case L3_CONFIG_DATA_TYPE_SHARED_4:
case L3_CONFIG_DATA_TYPE_DEVIP_4:
case L3_CONFIG_DATA_TYPE_AC_6:
case L3_CONFIG_DATA_TYPE_DHCP_6:
case L3_CONFIG_DATA_TYPE_DEVIP_6:
*out_acd_timeout_msec = _prop_get_ipv4_dad_timeout(self);
goto after_acd_timeout;
case L3_CONFIG_DATA_TYPE_DHCP_4:
/* For DHCP, we perform ACD separately, because we want to decline the
* lease in case of a conflict. */
*out_acd_timeout_msec = 0;
goto after_acd_timeout;
case _L3_CONFIG_DATA_TYPE_NUM:
case _L3_CONFIG_DATA_TYPE_NONE:
case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
break;
}
*out_acd_timeout_msec = nm_assert_unreachable_val(0);
after_acd_timeout:
switch (type) {
case L3_CONFIG_DATA_TYPE_LL_4:
*out_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_ONCE;
goto after_acd_defend_type;
case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
case L3_CONFIG_DATA_TYPE_MANUALIP:
case L3_CONFIG_DATA_TYPE_LL_6:
case L3_CONFIG_DATA_TYPE_PD_6:
case L3_CONFIG_DATA_TYPE_SHARED_4:
case L3_CONFIG_DATA_TYPE_DHCP_4:
case L3_CONFIG_DATA_TYPE_DEVIP_4:
case L3_CONFIG_DATA_TYPE_AC_6:
case L3_CONFIG_DATA_TYPE_DHCP_6:
case L3_CONFIG_DATA_TYPE_DEVIP_6:
*out_acd_defend_type = NM_L3_ACD_DEFEND_TYPE_ALWAYS;
goto after_acd_defend_type;
case _L3_CONFIG_DATA_TYPE_NUM:
case _L3_CONFIG_DATA_TYPE_NONE:
case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
break;
}
*out_acd_defend_type = nm_assert_unreachable_val(NM_L3_ACD_DEFEND_TYPE_ALWAYS);
after_acd_defend_type:
switch (type) {
case L3_CONFIG_DATA_TYPE_DEVIP_UNSPEC:
case L3_CONFIG_DATA_TYPE_MANUALIP:
case L3_CONFIG_DATA_TYPE_LL_4:
case L3_CONFIG_DATA_TYPE_LL_6:
case L3_CONFIG_DATA_TYPE_PD_6:
case L3_CONFIG_DATA_TYPE_SHARED_4:
*out_merge_flags = NM_L3_CONFIG_MERGE_FLAGS_NONE;
goto after_merge_flags;
case L3_CONFIG_DATA_TYPE_DHCP_4:
case L3_CONFIG_DATA_TYPE_DEVIP_4:
*out_merge_flags = priv->l3config_merge_flags_4;
goto after_merge_flags;
case L3_CONFIG_DATA_TYPE_AC_6:
case L3_CONFIG_DATA_TYPE_DHCP_6:
case L3_CONFIG_DATA_TYPE_DEVIP_6:
*out_merge_flags = priv->l3config_merge_flags_6;
goto after_merge_flags;
case _L3_CONFIG_DATA_TYPE_NUM:
case _L3_CONFIG_DATA_TYPE_NONE:
case _L3_CONFIG_DATA_TYPE_ACD_ONLY:
break;
}
*out_merge_flags = nm_assert_unreachable_val(NM_L3_CONFIG_MERGE_FLAGS_NONE);
after_merge_flags:
return;
}
static gboolean
_dev_l3_register_l3cds_add_config(NMDevice *self,
L3ConfigDataType l3cd_type,
NML3CfgConfigFlags flags)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NML3ConfigMergeFlags merge_flags;
NML3AcdDefendType acd_defend_type;
guint32 acd_timeout_msec;
_dev_l3_get_config_settings(self, l3cd_type, &merge_flags, &acd_defend_type, &acd_timeout_msec);
return nm_l3cfg_add_config(priv->l3cfg,
_dev_l3_config_data_tag_get(priv, l3cd_type),
FALSE,
priv->l3cds[l3cd_type].d,
l3cd_type,
nm_device_get_route_table(self, AF_INET),
nm_device_get_route_table(self, AF_INET6),
nm_device_get_route_metric(self, AF_INET),
nm_device_get_route_metric(self, AF_INET6),
_dev_default_route_metric_penalty_get(self, AF_INET),
_dev_default_route_metric_penalty_get(self, AF_INET6),
_prop_get_ipvx_dns_priority(self, AF_INET),
_prop_get_ipvx_dns_priority(self, AF_INET6),
acd_defend_type,
acd_timeout_msec,
flags,
merge_flags);
}
static gboolean
_dev_l3_register_l3cds_set_one_full(NMDevice *self,
L3ConfigDataType l3cd_type,
const NML3ConfigData *l3cd,
NML3CfgConfigFlags flags,
NMTernary commit_sync)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
gboolean changed = FALSE;
if (priv->l3cds[l3cd_type].d != l3cd) {
if (nm_l3_config_data_equal(priv->l3cds[l3cd_type].d, l3cd)) {
/* we would set to a different instance, but the same content!
* We keep the previous one and ignore the new @l3cd.
*
* Warning: this means, that after calling this function,
* priv->l3cds[l3cd_type].d still might point to a different
* (though semantically equal) l3cd instance. */
} else {
l3cd_old = g_steal_pointer(&priv->l3cds[l3cd_type].d);
if (l3cd)
priv->l3cds[l3cd_type].d = nm_l3_config_data_ref_and_seal(l3cd);
}
}
if (priv->l3cfg) {
if (priv->l3cds[l3cd_type].d) {
if (_dev_l3_register_l3cds_add_config(self, l3cd_type, flags))
changed = TRUE;
}
if (l3cd_old) {
if (nm_l3cfg_remove_config(priv->l3cfg,
_dev_l3_config_data_tag_get(priv, l3cd_type),
l3cd_old))
changed = TRUE;
}
}
if (changed && commit_sync != NM_TERNARY_DEFAULT)
_dev_l3_cfg_commit(self, !!commit_sync);
return changed;
}
static gboolean
_dev_l3_register_l3cds_set_one(NMDevice *self,
L3ConfigDataType l3cd_type,
const NML3ConfigData *l3cd,
NMTernary commit_sync)
{
return _dev_l3_register_l3cds_set_one_full(self,
l3cd_type,
l3cd,
NM_L3CFG_CONFIG_FLAGS_NONE,
commit_sync);
}
static void
_dev_l3_update_l3cds_ifindex(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int ip_ifindex;
int i;
ip_ifindex = nm_device_get_ip_ifindex(self);
if (ip_ifindex <= 0)
return;
for (i = 0; i < (int) G_N_ELEMENTS(priv->l3cds); i++) {
if (priv->l3cds[i].d && nm_l3_config_data_get_ifindex(priv->l3cds[i].d) != ip_ifindex) {
nm_auto_unref_l3cd const NML3ConfigData *l3cd_old = NULL;
l3cd_old = g_steal_pointer(&priv->l3cds[i].d);
priv->l3cds[i].d =
nm_l3_config_data_seal(nm_l3_config_data_new_clone(l3cd_old, ip_ifindex));
}
}
}
static gboolean
_dev_l3_register_l3cds(NMDevice *self,
NML3Cfg *l3cfg,
gboolean do_add /* else remove */,
NMTernary do_commit)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean is_external;
gboolean changed;
int i;
if (!l3cfg)
return FALSE;
is_external = nm_device_sys_iface_state_is_external(self);
changed = FALSE;
for (i = 0; i < (int) G_N_ELEMENTS(priv->l3cds); i++) {
if (!priv->l3cds[i].d)
continue;
if (!do_add) {
if (nm_l3cfg_remove_config(l3cfg,
_dev_l3_config_data_tag_get(priv, i),
priv->l3cds[i].d))
changed = TRUE;
continue;
}
if (is_external)
continue;
if (_dev_l3_register_l3cds_add_config(self, i, NM_L3CFG_CONFIG_FLAGS_NONE))
changed = TRUE;
}
if (do_commit == NM_TERNARY_DEFAULT)
do_commit = changed;
if (do_commit)
_dev_l3_cfg_commit(self, TRUE);
return changed;
}
/*****************************************************************************/
void
nm_device_l3cfg_commit(NMDevice *self, NML3CfgCommitType commit_type, gboolean commit_sync)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->l3cfg)
return;
/* FIXME(l3cfg): commit_sync should go away and not be used. The reason is that
* a commit does *a lot* of things which are outside the control of the caller,
* which makes it unsuitable to call in most cases. */
if (!commit_sync) {
nm_l3cfg_commit_on_idle_schedule(priv->l3cfg, commit_type);
return;
}
nm_l3cfg_commit(priv->l3cfg, commit_type);
}
static void
_dev_l3_cfg_commit(NMDevice *self, gboolean commit_sync)
{
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_AUTO, commit_sync);
}
static void
update_external_connection(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingsConnection *settings_connection;
gs_unref_object NMConnection *connection_new = NULL;
NMConnection *connection_old;
gs_unref_object NMSetting *s_ip4_new = NULL;
gs_unref_object NMSetting *s_ip6_new = NULL;
NMSetting *s_ip4_old;
NMSetting *s_ip6_old;
/* Update external connections with configuration from platform */
if (!nm_device_sys_iface_state_is_external(self))
return;
settings_connection = nm_device_get_settings_connection(self);
if (!settings_connection)
return;
if (!NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection),
NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL))
return;
if (nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(priv->act_request.obj))
!= NM_ACTIVATION_TYPE_EXTERNAL)
return;
connection_old = nm_settings_connection_get_connection(settings_connection);
s_ip4_old = nm_connection_get_setting(connection_old, NM_TYPE_SETTING_IP4_CONFIG);
s_ip6_old = nm_connection_get_setting(connection_old, NM_TYPE_SETTING_IP6_CONFIG);
s_ip4_new = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
AF_INET,
nm_device_get_ip_ifindex(self),
FALSE);
s_ip6_new = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
AF_INET6,
nm_device_get_ip_ifindex(self),
_get_maybe_ipv6_disabled(self));
if (!s_ip4_old || !nm_setting_compare(s_ip4_new, s_ip4_old, NM_SETTING_COMPARE_FLAG_EXACT)) {
connection_new = nm_simple_connection_new_clone(connection_old);
nm_connection_add_setting(connection_new, g_steal_pointer(&s_ip4_new));
}
if (!s_ip6_old || !nm_setting_compare(s_ip6_new, s_ip6_old, NM_SETTING_COMPARE_FLAG_EXACT)) {
if (!connection_new)
connection_new = nm_simple_connection_new_clone(connection_old);
nm_connection_add_setting(connection_new, g_steal_pointer(&s_ip6_new));
}
if (connection_new) {
nm_settings_connection_update(settings_connection,
connection_new,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_UPDATE_NON_SECRET,
"update-external",
NULL);
}
}
static void
_dev_l3_cfg_notify_cb(NML3Cfg *l3cfg, const NML3ConfigNotifyData *notify_data, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(l3cfg == priv->l3cfg);
switch (notify_data->notify_type) {
case NM_L3_CONFIG_NOTIFY_TYPE_L3CD_CHANGED:
if (notify_data->l3cd_changed.commited) {
g_signal_emit(self,
signals[L3CD_CHANGED],
0,
notify_data->l3cd_changed.l3cd_old,
notify_data->l3cd_changed.l3cd_new);
}
return;
case NM_L3_CONFIG_NOTIFY_TYPE_ACD_EVENT:
{
const NML3AcdAddrInfo *addr_info = &notify_data->acd_event.info;
if (addr_info->state > NM_L3_ACD_ADDR_STATE_PROBING)
_dev_ipmanual_check_ready(self);
return;
}
case NM_L3_CONFIG_NOTIFY_TYPE_PRE_COMMIT:
{
const NML3ConfigData *l3cd;
/* FIXME(l3cfg): MTU handling should be moved to l3cfg. */
l3cd = nm_l3cfg_get_combined_l3cd(l3cfg, TRUE);
if (l3cd)
priv->ip6_mtu = nm_l3_config_data_get_ip6_mtu(l3cd);
_commit_mtu(self);
return;
}
case NM_L3_CONFIG_NOTIFY_TYPE_POST_COMMIT:
if (priv->ipshared_data_4.state == NM_DEVICE_IP_STATE_PENDING
&& !priv->ipshared_data_4.v4.dnsmasq_manager && priv->ipshared_data_4.v4.l3cd) {
_dev_ipshared4_spawn_dnsmasq(self);
nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
}
_dev_ipmanual_check_ready(self);
return;
case NM_L3_CONFIG_NOTIFY_TYPE_IPV4LL_EVENT:
nm_assert(NM_IS_L3_IPV4LL(notify_data->ipv4ll_event.ipv4ll));
if (priv->ipll_data_4.v4.ipv4ll == notify_data->ipv4ll_event.ipv4ll)
_dev_ipll4_notify_event(self);
return;
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE:
return;
case NM_L3_CONFIG_NOTIFY_TYPE_ROUTES_TEMPORARY_NOT_AVAILABLE_EXPIRED:
/* we commit again. This way we try to configure the routes.*/
_dev_l3_cfg_commit(self, FALSE);
return;
case NM_L3_CONFIG_NOTIFY_TYPE_PLATFORM_CHANGE_ON_IDLE:
if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
nmp_object_type_to_flags(NMP_OBJECT_TYPE_LINK)
| nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ADDRESS)
| nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS)))
_dev_unmanaged_check_external_down(self, TRUE, TRUE);
if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP4_ADDRESS)
| nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS))) {
g_signal_emit(self, signals[PLATFORM_ADDRESS_CHANGED], 0);
}
/* Check if AC6 addresses completed DAD */
if (NM_FLAGS_ANY(notify_data->platform_change_on_idle.obj_type_flags,
nmp_object_type_to_flags(NMP_OBJECT_TYPE_IP6_ADDRESS))
&& priv->ipac6_data.state == NM_DEVICE_IP_STATE_PENDING && priv->ipac6_data.l3cd
&& nm_l3cfg_check_ready(l3cfg,
priv->ipac6_data.l3cd,
AF_INET6,
NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY,
NULL)) {
if (nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, AF_INET6))
_dev_l3_cfg_commit(self, FALSE);
nm_clear_l3cd(&priv->ipac6_data.l3cd);
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_INET6);
}
_dev_ipmanual_check_ready(self);
update_external_connection(self);
nm_device_queue_recheck_assume(self);
return;
case _NM_L3_CONFIG_NOTIFY_TYPE_NUM:
break;
}
nm_assert_not_reached();
}
static void
_dev_l3_cfg_commit_type_reset(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NML3CfgCommitType commit_type;
if (!priv->l3cfg)
return;
switch (priv->sys_iface_state) {
case NM_DEVICE_SYS_IFACE_STATE_EXTERNAL:
case NM_DEVICE_SYS_IFACE_STATE_REMOVED:
commit_type = NM_L3_CFG_COMMIT_TYPE_NONE;
goto do_set;
case NM_DEVICE_SYS_IFACE_STATE_ASSUME:
commit_type = NM_L3_CFG_COMMIT_TYPE_ASSUME;
goto do_set;
case NM_DEVICE_SYS_IFACE_STATE_MANAGED:
commit_type = NM_L3_CFG_COMMIT_TYPE_UPDATE;
goto do_set;
}
nm_assert_not_reached();
return;
do_set:
priv->l3cfg_commit_type =
nm_l3cfg_commit_type_register(priv->l3cfg, commit_type, priv->l3cfg_commit_type, "device");
if (commit_type == NM_L3_CFG_COMMIT_TYPE_NONE)
nm_l3cfg_commit_type_reset_update(priv->l3cfg);
}
/*****************************************************************************/
const char *
nm_device_get_udi(NMDevice *self)
{
g_return_val_if_fail(self != NULL, NULL);
return NM_DEVICE_GET_PRIVATE(self)->udi;
}
const char *
nm_device_get_iface(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->iface;
}
static gboolean
_set_ifindex(NMDevice *self, int ifindex, gboolean is_ip_ifindex)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_unref_object NML3Cfg *l3cfg_old = NULL;
NML3CfgCommitTypeHandle *l3cfg_commit_type_old = NULL;
gboolean l3_changed;
int ip_ifindex_new;
int *p_ifindex;
gboolean l3cfg_was_reset = FALSE;
if (ifindex < 0) {
nm_assert_not_reached();
ifindex = 0;
}
p_ifindex = is_ip_ifindex ? &priv->ip_ifindex_ : &priv->ifindex_;
if (*p_ifindex == ifindex)
return FALSE;
*p_ifindex = ifindex;
ip_ifindex_new = nm_device_get_ip_ifindex(self);
if (priv->l3cfg) {
if (ip_ifindex_new <= 0 || ip_ifindex_new != nm_l3cfg_get_ifindex(priv->l3cfg)) {
const NML3ConfigData *l3cd_old;
if (ip_ifindex_new <= 0) {
/* The ifindex was reset. Send a last L3CD_CHANGED
* signal with a NULL l3cd so that the old one can
* be removed from the DNS manager.
*/
l3cd_old = nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE);
if (l3cd_old)
g_signal_emit(self, signals[L3CD_CHANGED], 0, l3cd_old, NULL);
}
g_signal_handlers_disconnect_by_func(priv->l3cfg,
G_CALLBACK(_dev_l3_cfg_notify_cb),
self);
l3cfg_old = g_steal_pointer(&priv->l3cfg_);
l3cfg_commit_type_old = g_steal_pointer(&priv->l3cfg_commit_type);
l3cfg_was_reset = TRUE;
}
}
if (!priv->l3cfg && ip_ifindex_new > 0) {
priv->l3cfg_ = nm_netns_l3cfg_acquire(priv->netns, ip_ifindex_new);
g_signal_connect(priv->l3cfg,
NM_L3CFG_SIGNAL_NOTIFY,
G_CALLBACK(_dev_l3_cfg_notify_cb),
self);
_dev_l3_cfg_commit_type_reset(self);
l3cfg_was_reset = TRUE;
}
if (!priv->l3cfg) {
_cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_KEEP, FALSE);
_cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_KEEP, FALSE);
}
_LOGD(LOGD_DEVICE,
"ifindex: set %sifindex %d%s%s%s%s%s%s",
is_ip_ifindex ? "ip-" : "",
ifindex,
NM_PRINT_FMT_QUOTED(l3cfg_old && l3cfg_old != priv->l3cfg,
" (old-l3cfg: ",
nm_hash_obfuscated_ptr_str_a(l3cfg_old),
")",
""),
NM_PRINT_FMT_QUOTED(priv->l3cfg && l3cfg_old != priv->l3cfg,
" (l3cfg: ",
nm_hash_obfuscated_ptr_str_a(priv->l3cfg),
")",
""));
if (priv->manager)
nm_manager_emit_device_ifindex_changed(priv->manager, self);
if (!is_ip_ifindex)
_notify(self, PROP_IFINDEX);
if (l3cfg_was_reset) {
gs_unref_object NMIPConfig *ipconf_old_4 = NULL;
gs_unref_object NMIPConfig *ipconf_old_6 = NULL;
ipconf_old_4 = g_steal_pointer(&priv->l3ipdata_4.ip_config);
ipconf_old_6 = g_steal_pointer(&priv->l3ipdata_6.ip_config);
if (priv->l3cfg) {
priv->l3ipdata_4.ip_config = nm_l3cfg_ipconfig_acquire(priv->l3cfg, AF_INET);
priv->l3ipdata_6.ip_config = nm_l3cfg_ipconfig_acquire(priv->l3cfg, AF_INET6);
}
_notify(self, PROP_IP4_CONFIG);
_notify(self, PROP_IP6_CONFIG);
}
if (l3cfg_old != priv->l3cfg) {
l3_changed = FALSE;
if (_dev_l3_register_l3cds(self, l3cfg_old, FALSE, FALSE))
l3_changed = TRUE;
/* Now it gets ugly. We changed the ip-ifindex, which determines the NML3Cfg instance.
* But all the NML3ConfigData we currently track are still for the old ifindex. We
* need to update them.
*
* This should be all handled entirely different, where an NMDevice is strictly
* associated with one ifindex (and not the ifindex/ip-ifindex split). Or it
* is not at all associated with an ifindex, but only a controlling device for
* a real NMDevice (that has the ifindex). */
_dev_l3_update_l3cds_ifindex(self);
if (_dev_l3_register_l3cds(self, priv->l3cfg, TRUE, FALSE))
l3_changed = TRUE;
if (l3_changed)
_dev_l3_cfg_commit(self, TRUE);
}
if (l3cfg_commit_type_old)
nm_l3cfg_commit_type_unregister(l3cfg_old, l3cfg_commit_type_old);
update_prop_ip_iface(self);
return TRUE;
}
/**
* nm_device_take_over_link:
* @self: the #NMDevice
* @ifindex: a ifindex
* @old_name: (transfer full): on return, the name of the old link, if
* the link was renamed
* @error: location to store error, or %NULL
*
* Given an existing link, move it under the control of a device. In
* particular, the link will be renamed to match the device name. If the
* link was renamed, the old name is returned in @old_name.
*
* Returns: %TRUE if the device took control of the link, %FALSE otherwise
*/
gboolean
nm_device_take_over_link(NMDevice *self, int ifindex, char **old_name, GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NMPlatformLink *plink;
NMPlatform *platform;
nm_assert(ifindex > 0);
NM_SET_OUT(old_name, NULL);
if (priv->ifindex > 0 && priv->ifindex != ifindex) {
nm_utils_error_set(error,
NM_UTILS_ERROR_UNKNOWN,
"the device already has ifindex %d",
priv->ifindex);
return FALSE;
}
platform = nm_device_get_platform(self);
plink = nm_platform_link_get(platform, ifindex);
if (!plink) {
nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "link %d not found", ifindex);
return FALSE;
}
if (!nm_streq(plink->name, nm_device_get_iface(self))) {
gboolean up;
gboolean success;
gs_free char *name = NULL;
up = NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP);
name = g_strdup(plink->name);
/* Rename the link to the device ifname */
if (up)
nm_platform_link_change_flags(platform, ifindex, IFF_UP, FALSE);
success = nm_platform_link_set_name(platform, ifindex, nm_device_get_iface(self));
if (up)
nm_platform_link_change_flags(platform, ifindex, IFF_UP, TRUE);
if (!success) {
nm_utils_error_set(error, NM_UTILS_ERROR_UNKNOWN, "failure renaming link %d", ifindex);
return FALSE;
}
NM_SET_OUT(old_name, g_steal_pointer(&name));
}
_set_ifindex(self, ifindex, FALSE);
return TRUE;
}
int
nm_device_get_ifindex(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), 0);
return NM_DEVICE_GET_PRIVATE(self)->ifindex;
}
/**
* nm_device_is_software:
* @self: the #NMDevice
*
* Indicates if the device is a software-based virtual device without
* backing hardware, which can be added and removed programmatically.
*
* Returns: %TRUE if the device is a software-based device
*/
gboolean
nm_device_is_software(NMDevice *self)
{
return NM_FLAGS_HAS(NM_DEVICE_GET_PRIVATE(self)->capabilities, NM_DEVICE_CAP_IS_SOFTWARE);
}
/**
* nm_device_is_real:
* @self: the #NMDevice
*
* Returns: %TRUE if the device exists, %FALSE if the device is a placeholder
*/
gboolean
nm_device_is_real(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return NM_DEVICE_GET_PRIVATE(self)->real;
}
const char *
nm_device_get_ip_iface(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail(self != NULL, NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
/* If it's not set, default to iface */
return priv->ip_iface ?: priv->iface;
}
const char *
nm_device_get_ip_iface_from_platform(NMDevice *self)
{
int ifindex;
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
return NULL;
return nm_platform_link_get_name(nm_device_get_platform(self), ifindex);
}
int
nm_device_get_ip_ifindex(const NMDevice *self)
{
const NMDevicePrivate *priv;
g_return_val_if_fail(self != NULL, 0);
priv = NM_DEVICE_GET_PRIVATE(self);
/* If it's not set, default to ifindex */
return priv->ip_iface ? priv->ip_ifindex : priv->ifindex;
}
static void
_set_ip_ifindex(NMDevice *self, int ifindex, const char *ifname)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatform *platform;
gboolean eq_name;
/* normalize arguments */
if (ifindex <= 0) {
ifindex = 0;
ifname = NULL;
}
eq_name = nm_streq0(priv->ip_iface, ifname);
if (eq_name && priv->ip_ifindex == ifindex)
return;
_LOGD(LOGD_DEVICE,
"ip-ifindex: update ip-interface to %s%s%s, ifindex %d",
NM_PRINT_FMT_QUOTE_STRING(ifname),
ifindex);
if (!eq_name) {
g_free(priv->ip_iface_);
priv->ip_iface_ = g_strdup(ifname);
update_prop_ip_iface(self);
}
_set_ifindex(self, ifindex, TRUE);
if (priv->ip_ifindex > 0) {
platform = nm_device_get_platform(self);
nm_platform_process_events_ensure_link(platform, priv->ip_ifindex, priv->ip_iface);
nm_platform_link_set_inet6_addr_gen_mode(platform,
priv->ip_ifindex,
NM_IN6_ADDR_GEN_MODE_NONE);
if (!nm_platform_link_is_up(platform, priv->ip_ifindex))
nm_platform_link_change_flags(platform, priv->ip_ifindex, IFF_UP, TRUE);
}
/* We don't care about any saved values from the old iface */
g_hash_table_remove_all(priv->ip6_saved_properties);
}
gboolean
nm_device_set_ip_ifindex(NMDevice *self, int ifindex)
{
char ifname_buf[IFNAMSIZ];
const char *ifname = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(nm_device_is_activating(self), FALSE);
if (ifindex > 0) {
ifname = nm_platform_if_indextoname(nm_device_get_platform(self), ifindex, ifname_buf);
if (!ifname)
_LOGW(LOGD_DEVICE, "ip-ifindex: ifindex %d not found", ifindex);
}
_set_ip_ifindex(self, ifindex, ifname);
return ifindex > 0;
}
/**
* nm_device_set_ip_iface:
* @self: the #NMDevice
* @ifname: the new IP interface name
*
* Updates the IP interface name and possibly the ifindex.
*
* Returns: %TRUE if an interface with name @ifname exists,
* and %FALSE, if @ifname is %NULL or no such interface exists.
*/
gboolean
nm_device_set_ip_iface(NMDevice *self, const char *ifname)
{
int ifindex = 0;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(nm_device_is_activating(self), FALSE);
if (ifname) {
ifindex = nm_platform_if_nametoindex(nm_device_get_platform(self), ifname);
if (ifindex <= 0)
_LOGW(LOGD_DEVICE, "ip-ifindex: ifname %s not found", ifname);
}
_set_ip_ifindex(self, ifindex, ifname);
return ifindex > 0;
}
/*****************************************************************************/
int
nm_device_parent_get_ifindex(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), 0);
priv = NM_DEVICE_GET_PRIVATE(self);
return priv->parent_ifindex;
}
NMDevice *
nm_device_parent_get_device(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->parent_device.obj;
}
static void
parent_changed_notify(NMDevice *self,
int old_ifindex,
NMDevice *old_parent,
int new_ifindex,
NMDevice *new_parent)
{
/* empty handler to allow subclasses to always chain up the virtual function. */
}
static gboolean
_parent_set_ifindex(NMDevice *self, int parent_ifindex, gboolean force_check)
{
NMDevicePrivate *priv;
NMDevice *parent_device;
gboolean changed = FALSE;
int old_ifindex;
gs_unref_object NMDevice *old_device = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (parent_ifindex <= 0)
parent_ifindex = 0;
old_ifindex = priv->parent_ifindex;
if (priv->parent_ifindex == parent_ifindex) {
if (parent_ifindex > 0) {
if (!force_check && priv->parent_device.obj
&& nm_device_get_ifindex(priv->parent_device.obj) == parent_ifindex)
return FALSE;
} else {
if (!priv->parent_device.obj)
return FALSE;
}
} else {
priv->parent_ifindex = parent_ifindex;
changed = TRUE;
}
if (parent_ifindex > 0) {
parent_device = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, parent_ifindex);
if (parent_device == self)
parent_device = NULL;
} else
parent_device = NULL;
if (parent_device != priv->parent_device.obj) {
old_device = nm_g_object_ref(priv->parent_device.obj);
nm_dbus_track_obj_path_set(&priv->parent_device, parent_device, TRUE);
changed = TRUE;
}
if (changed) {
if (priv->parent_ifindex <= 0)
_LOGD(LOGD_DEVICE, "parent: clear");
else if (!priv->parent_device.obj)
_LOGD(LOGD_DEVICE, "parent: ifindex %d, no device", priv->parent_ifindex);
else {
_LOGD(LOGD_DEVICE,
"parent: ifindex %d, device %p, %s",
priv->parent_ifindex,
priv->parent_device.obj,
nm_device_get_iface(priv->parent_device.obj));
}
NM_DEVICE_GET_CLASS(self)->parent_changed_notify(self,
old_ifindex,
old_device,
priv->parent_ifindex,
priv->parent_device.obj);
}
return changed;
}
void
nm_device_parent_set_ifindex(NMDevice *self, int parent_ifindex)
{
_parent_set_ifindex(self, parent_ifindex, FALSE);
}
gboolean
nm_device_parent_notify_changed(NMDevice *self, NMDevice *change_candidate, gboolean device_removed)
{
NMDevicePrivate *priv;
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_IS_DEVICE(change_candidate));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->parent_ifindex > 0) {
if (priv->parent_device.obj == change_candidate
|| priv->parent_ifindex == nm_device_get_ifindex(change_candidate))
return _parent_set_ifindex(self, priv->parent_ifindex, device_removed);
}
return FALSE;
}
/*****************************************************************************/
const char *
nm_device_parent_find_for_connection(NMDevice *self, const char *current_setting_parent)
{
const char *new_parent;
NMDevice *parent_device;
parent_device = nm_device_parent_get_device(self);
if (!parent_device)
return NULL;
new_parent = nm_device_get_iface(parent_device);
if (!new_parent)
return NULL;
if (current_setting_parent && !nm_streq(current_setting_parent, new_parent)
&& nm_utils_is_uuid(current_setting_parent)) {
NMSettingsConnection *parent_connection;
/* Don't change a parent specified by UUID if it's still valid */
parent_connection = nm_settings_get_connection_by_uuid(nm_device_get_settings(self),
current_setting_parent);
if (parent_connection
&& nm_device_check_connection_compatible(
parent_device,
nm_settings_connection_get_connection(parent_connection),
NULL))
return current_setting_parent;
}
return new_parent;
}
/*****************************************************************************/
static void
_stats_update_counters(NMDevice *self, guint64 tx_bytes, guint64 rx_bytes)
{
NMDevicePrivate *priv;
gboolean tx_changed = FALSE;
gboolean rx_changed = FALSE;
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->stats.tx_bytes != tx_bytes) {
priv->stats.tx_bytes = tx_bytes;
tx_changed = TRUE;
}
if (priv->stats.rx_bytes != rx_bytes) {
priv->stats.rx_bytes = rx_bytes;
rx_changed = TRUE;
}
nm_gobject_notify_together(self,
tx_changed ? PROP_STATISTICS_TX_BYTES : PROP_0,
rx_changed ? PROP_STATISTICS_RX_BYTES : PROP_0);
}
static void
_stats_update_counters_from_pllink(NMDevice *self, const NMPlatformLink *pllink)
{
_stats_update_counters(self, pllink->tx_bytes, pllink->rx_bytes);
}
static gboolean
_stats_timeout_cb(gpointer user_data)
{
NMDevice *self = user_data;
int ifindex;
ifindex = nm_device_get_ip_ifindex(self);
_LOGT(LOGD_DEVICE, "stats: refresh %d", ifindex);
if (ifindex > 0)
nm_platform_link_refresh(nm_device_get_platform(self), ifindex);
return G_SOURCE_CONTINUE;
}
static guint
_stats_refresh_rate_real(guint refresh_rate_ms)
{
const guint STATS_REFRESH_RATE_MS_MIN = 200;
if (refresh_rate_ms == 0)
return 0;
if (refresh_rate_ms < STATS_REFRESH_RATE_MS_MIN) {
/* you cannot set the refresh-rate arbitrarily small. E.g.
* setting to 1ms is just killing. Have a lowest number. */
return STATS_REFRESH_RATE_MS_MIN;
}
return refresh_rate_ms;
}
static void
_stats_set_refresh_rate(NMDevice *self, guint refresh_rate_ms)
{
NMDevicePrivate *priv;
int ifindex;
guint old_rate;
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->stats.refresh_rate_ms == refresh_rate_ms)
return;
old_rate = priv->stats.refresh_rate_ms;
priv->stats.refresh_rate_ms = refresh_rate_ms;
_notify(self, PROP_STATISTICS_REFRESH_RATE_MS);
_LOGD(LOGD_DEVICE, "stats: set refresh to %u ms", priv->stats.refresh_rate_ms);
if (!nm_device_is_real(self))
return;
refresh_rate_ms = _stats_refresh_rate_real(refresh_rate_ms);
if (_stats_refresh_rate_real(old_rate) == refresh_rate_ms)
return;
nm_clear_g_source_inst(&priv->stats.timeout_source);
if (!refresh_rate_ms)
return;
/* trigger an initial refresh of the data whenever the refresh-rate changes.
* As we process the result in an idle handler with device_link_changed(),
* we don't get the result right away. */
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex > 0)
nm_platform_link_refresh(nm_device_get_platform(self), ifindex);
priv->stats.timeout_source = nm_g_timeout_add_source(refresh_rate_ms, _stats_timeout_cb, self);
}
/*****************************************************************************/
static gboolean
get_ip_iface_identifier(NMDevice *self, NMUtilsIPv6IfaceId *out_iid)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatform *platform = nm_device_get_platform(self);
const NMPlatformLink *pllink;
const guint8 *hwaddr;
guint8 pseudo_hwaddr[ETH_ALEN];
gsize hwaddr_len;
int ifindex;
gboolean success;
/* If we get here, we *must* have a kernel netdev, which implies an ifindex */
ifindex = nm_device_get_ip_ifindex(self);
g_return_val_if_fail(ifindex > 0, FALSE);
pllink = nm_platform_link_get(platform, ifindex);
if (!pllink || NM_IN_SET(pllink->type, NM_LINK_TYPE_NONE, NM_LINK_TYPE_UNKNOWN))
return FALSE;
hwaddr = nmp_link_address_get(&pllink->l_address, &hwaddr_len);
if (hwaddr_len <= 0)
return FALSE;
if (pllink->type == NM_LINK_TYPE_6LOWPAN) {
/* If the underlying IEEE 802.15.4 device has a short address we generate
* a "pseudo 48-bit address" that's to be used in the same fashion as a
* wired Ethernet address. The mechanism is specified in Section 6. of
* RFC 4944 */
guint16 pan_id;
guint16 short_addr;
short_addr = nm_platform_wpan_get_short_addr(platform, pllink->parent);
if (short_addr != G_MAXUINT16) {
pan_id = nm_platform_wpan_get_pan_id(platform, pllink->parent);
pseudo_hwaddr[0] = short_addr & 0xff;
pseudo_hwaddr[1] = (short_addr >> 8) & 0xff;
pseudo_hwaddr[2] = 0;
pseudo_hwaddr[3] = 0;
pseudo_hwaddr[4] = pan_id & 0xff;
pseudo_hwaddr[5] = (pan_id >> 8) & 0xff;
hwaddr = pseudo_hwaddr;
hwaddr_len = G_N_ELEMENTS(pseudo_hwaddr);
}
}
success = nm_utils_get_ipv6_interface_identifier(pllink->type,
hwaddr,
hwaddr_len,
priv->dev_id,
out_iid);
if (!success) {
_LOGW(LOGD_PLATFORM,
"failed to generate interface identifier "
"for link type %u hwaddr_len %zu",
pllink->type,
hwaddr_len);
}
return success;
}
/**
* nm_device_get_ip_iface_identifier:
* @self: an #NMDevice
* @iid: where to place the interface identifier
* @ignore_token: force creation of a non-tokenized address
* @out_is_token: on return, whether the identifier is tokenized
*
* Return the interface's identifier for the EUI64 address generation mode.
* It's either a manually set token or and identifier generated in a
* hardware-specific way.
*
* Unless @ignore_token is set the token is preferred. That is the case
* for link-local addresses (to mimic kernel behavior).
*
* Returns: #TRUE if the @iid could be set
*/
static gboolean
nm_device_get_ip_iface_identifier(NMDevice *self,
NMUtilsIPv6IfaceId *iid,
gboolean ignore_token,
gboolean *out_is_token)
{
NMSettingIP6Config *s_ip6;
const char *token = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
NM_SET_OUT(out_is_token, FALSE);
if (!ignore_token) {
s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
g_return_val_if_fail(s_ip6, FALSE);
token = nm_setting_ip6_config_get_token(s_ip6);
if (token)
NM_SET_OUT(out_is_token, TRUE);
}
if (token)
return nm_utils_ipv6_interface_identifier_get_from_token(iid, token);
else
return NM_DEVICE_GET_CLASS(self)->get_ip_iface_identifier(self, iid);
}
const char *
nm_device_get_driver(NMDevice *self)
{
g_return_val_if_fail(self != NULL, NULL);
return NM_DEVICE_GET_PRIVATE(self)->driver;
}
const char *
nm_device_get_driver_version(NMDevice *self)
{
g_return_val_if_fail(self != NULL, NULL);
return NM_DEVICE_GET_PRIVATE(self)->driver_version;
}
NMDeviceType
nm_device_get_device_type(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_TYPE_UNKNOWN);
return NM_DEVICE_GET_PRIVATE(self)->type;
}
NMLinkType
nm_device_get_link_type(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_LINK_TYPE_UNKNOWN);
return NM_DEVICE_GET_PRIVATE(self)->link_type;
}
/**
* nm_device_get_metered:
* @setting: the #NMDevice
*
* Returns: the #NMDevice:metered property of the device.
*
* Since: 1.2
**/
NMMetered
nm_device_get_metered(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_METERED_UNKNOWN);
return NM_DEVICE_GET_PRIVATE(self)->metered;
}
guint32
nm_device_get_route_metric_default(NMDeviceType device_type)
{
/* Device 'priority' is used for the default route-metric and is based on
* the device type. The settings ipv4.route-metric and ipv6.route-metric
* can overwrite this default.
*
* For both IPv4 and IPv6 we use the same default values.
*
* The route-metric is used for the metric of the routes of device.
* This also applies to the default route. Therefore it affects also
* which device is the "best".
*
* For comparison, note that iproute2 by default adds IPv4 routes with
* metric 0, and IPv6 routes with metric 1024. The latter is the IPv6
* "user default" in the kernel (NM_PLATFORM_ROUTE_METRIC_DEFAULT_IP6).
* In kernel, the full uint32_t range is available for route
* metrics (except for IPv6, where 0 means 1024).
*/
switch (device_type) {
/* 50 is also used for VPN plugins (NM_VPN_ROUTE_METRIC_DEFAULT).
*
* Note that returning 50 from this function means that this device-type is
* in some aspects a VPN. */
case NM_DEVICE_TYPE_WIREGUARD:
return NM_VPN_ROUTE_METRIC_DEFAULT;
case NM_DEVICE_TYPE_ETHERNET:
case NM_DEVICE_TYPE_VETH:
return 100;
case NM_DEVICE_TYPE_MACSEC:
return 125;
case NM_DEVICE_TYPE_INFINIBAND:
return 150;
case NM_DEVICE_TYPE_ADSL:
return 200;
case NM_DEVICE_TYPE_WIMAX:
return 250;
case NM_DEVICE_TYPE_BOND:
return 300;
case NM_DEVICE_TYPE_TEAM:
return 350;
case NM_DEVICE_TYPE_VLAN:
return 400;
case NM_DEVICE_TYPE_MACVLAN:
return 410;
case NM_DEVICE_TYPE_BRIDGE:
return 425;
case NM_DEVICE_TYPE_TUN:
return 450;
case NM_DEVICE_TYPE_PPP:
return 460;
case NM_DEVICE_TYPE_VRF:
return 470;
case NM_DEVICE_TYPE_VXLAN:
return 500;
case NM_DEVICE_TYPE_DUMMY:
return 550;
case NM_DEVICE_TYPE_WIFI:
return 600;
case NM_DEVICE_TYPE_OLPC_MESH:
return 650;
case NM_DEVICE_TYPE_IP_TUNNEL:
return 675;
case NM_DEVICE_TYPE_MODEM:
return 700;
case NM_DEVICE_TYPE_BT:
return 750;
case NM_DEVICE_TYPE_6LOWPAN:
return 775;
case NM_DEVICE_TYPE_OVS_BRIDGE:
case NM_DEVICE_TYPE_OVS_INTERFACE:
case NM_DEVICE_TYPE_OVS_PORT:
return 800;
case NM_DEVICE_TYPE_WPAN:
return 850;
case NM_DEVICE_TYPE_WIFI_P2P:
case NM_DEVICE_TYPE_GENERIC:
return 950;
case NM_DEVICE_TYPE_UNKNOWN:
return 10000;
case NM_DEVICE_TYPE_UNUSED1:
case NM_DEVICE_TYPE_UNUSED2:
/* omit default: to get compiler warning about missing switch cases */
break;
}
return 11000;
}
static guint32
_dev_default_route_metric_penalty_get(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL
&& nm_connectivity_check_enabled(concheck_get_mgr(self)))
return 20000;
return 0;
}
guint32
nm_device_get_route_metric(NMDevice *self, int addr_family)
{
gint64 route_metric;
NMSettingIPConfig *s_ip;
NMConnection *connection;
const char *property;
g_return_val_if_fail(NM_IS_DEVICE(self), G_MAXUINT32);
g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), G_MAXUINT32);
connection = nm_device_get_applied_connection(self);
if (connection) {
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
/* Slave interfaces don't have IP settings, but we may get here when
* external changes are made or when noticing IP changes when starting
* the slave connection.
*/
if (s_ip) {
route_metric = nm_setting_ip_config_get_route_metric(s_ip);
if (route_metric >= 0)
goto out;
}
}
/* use the current NMConfigData, which makes this configuration reloadable.
* Note that that means that the route-metric might change between SIGHUP.
* You must cache the returned value if that is a problem. */
property = NM_IS_IPv4(addr_family) ? NM_CON_DEFAULT("ipv4.route-metric")
: NM_CON_DEFAULT("ipv6.route-metric");
route_metric = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
property,
self,
0,
G_MAXUINT32,
-1);
if (route_metric >= 0)
goto out;
route_metric = nm_manager_device_route_metric_reserve(NM_MANAGER_GET,
nm_device_get_ip_ifindex(self),
nm_device_get_device_type(self));
out:
return nm_utils_ip_route_metric_normalize(addr_family, route_metric);
}
guint32
nm_device_get_route_table(NMDevice *self, int addr_family)
{
guint32 route_table;
g_return_val_if_fail(NM_IS_DEVICE(self), RT_TABLE_MAIN);
route_table = _prop_get_ipvx_route_table(self, addr_family);
return route_table ?: (guint32) RT_TABLE_MAIN;
}
/* FIXME(l3cfg): need to properly handle the route-table sync mode and
* use it during commit. */
_nm_unused static NMIPRouteTableSyncMode
_get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean all_sync_now;
gboolean all_sync_eff;
all_sync_now = _prop_get_ipvx_route_table(self, addr_family) != 0u;
if (!all_sync_now) {
const NML3ConfigData *l3cd = priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d;
/* If there's a local route switch to all-sync in order
* to properly manage the local table */
all_sync_now = l3cd && nm_l3_config_data_has_routes_with_type_local(l3cd, addr_family);
}
if (all_sync_now)
all_sync_eff = TRUE;
else {
/* When we change from all-sync to no all-sync, we do a last all-sync one
* more time. For that, we determine the effective all-state based on the
* cached/previous all-sync flag.
*
* The purpose of this is to support reapply of route-table (and thus the
* all-sync mode). If reapply toggles from all-sync to no-all-sync, we must
* sync one last time. */
if (NM_IS_IPv4(addr_family))
all_sync_eff = priv->v4_route_table_all_sync_before;
else
all_sync_eff = priv->v6_route_table_all_sync_before;
}
if (NM_IS_IPv4(addr_family))
priv->v4_route_table_all_sync_before = all_sync_now;
else
priv->v6_route_table_all_sync_before = all_sync_now;
return all_sync_eff ? NM_IP_ROUTE_TABLE_SYNC_MODE_ALL : NM_IP_ROUTE_TABLE_SYNC_MODE_MAIN;
}
const NMPObject *
nm_device_get_best_default_route(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->l3cfg)
return NULL;
/* FIXME(l3cfg): this function returns the best default route that we
* *want* to configure. What is the meaning of that? Possibly the caller
* cares whether there *is* a default route configured, for which they
* should ask platform.
*
* Check callers why they call this. Quite possibly this whole notion of
* "has a default route" is wrong to being with, regardless whether we
* look at the desired or actual configuration. That is, because "has a default route"
* does not do justice to the complexity of routing (with policy routing,
* etc.). */
return nm_l3cfg_get_best_default_route(priv->l3cfg, addr_family, TRUE);
}
const char *
nm_device_get_type_desc(NMDevice *self)
{
g_return_val_if_fail(self != NULL, NULL);
return NM_DEVICE_GET_PRIVATE(self)->type_desc;
}
const char *
nm_device_get_type_description(NMDevice *self)
{
g_return_val_if_fail(self != NULL, NULL);
/* Beware: this function should return the same
* value as nm_device_get_type_description() in libnm. */
return NM_DEVICE_GET_CLASS(self)->get_type_description(self);
}
static const char *
get_type_description(NMDevice *self)
{
NMDeviceClass *klass;
nm_assert(NM_IS_DEVICE(self));
/* the default implementation for the description just returns the (modified)
* class name and depends entirely on the type of self. Note that we cache the
* description in the klass itself.
*
* Also note, that as the GObject class gets inited, it inherrits the fields
* of the parent class. That means, if NMDeviceVethClass was initialized after
* NMDeviceEthernetClass already has the description cached in the class
* (because we already fetched the description for an ethernet device),
* then default_type_description will wrongly contain "ethernet".
* To avoid that, and catch the situation, also cache the klass for
* which the description was cached. If that doesn't match, it was
* inherited and we need to reset it. */
klass = NM_DEVICE_GET_CLASS(self);
if (G_UNLIKELY(klass->default_type_description_klass != klass)) {
const char *typename;
gs_free char *s = NULL;
typename = G_OBJECT_TYPE_NAME(self);
if (g_str_has_prefix(typename, "NMDevice")) {
typename += 8;
if (nm_streq(typename, "Veth"))
typename = "Ethernet";
}
s = g_ascii_strdown(typename, -1);
klass->default_type_description = g_intern_string(s);
klass->default_type_description_klass = klass;
}
nm_assert(klass->default_type_description);
return klass->default_type_description;
}
gboolean
nm_device_has_carrier(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->carrier;
}
NMActRequest *
nm_device_get_act_request(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->act_request.obj;
}
NMActivationStateFlags
nm_device_get_activation_state_flags(NMDevice *self)
{
NMActRequest *ac;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_ACTIVATION_STATE_FLAG_NONE);
ac = NM_DEVICE_GET_PRIVATE(self)->act_request.obj;
if (!ac)
return NM_ACTIVATION_STATE_FLAG_NONE;
return nm_active_connection_get_state_flags(NM_ACTIVE_CONNECTION(ac));
}
NMSettingsConnection *
nm_device_get_settings_connection(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
return priv->act_request.obj ? nm_act_request_get_settings_connection(priv->act_request.obj)
: NULL;
}
NMConnection *
nm_device_get_settings_connection_get_connection(NMDevice *self)
{
NMSettingsConnection *sett_con;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->act_request.obj)
return NULL;
sett_con = nm_act_request_get_settings_connection(priv->act_request.obj);
if (!sett_con)
return NULL;
return nm_settings_connection_get_connection(sett_con);
}
NMConnection *
nm_device_get_applied_connection(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
return priv->act_request.obj ? nm_act_request_get_applied_connection(priv->act_request.obj)
: NULL;
}
gboolean
nm_device_has_unmodified_applied_connection(NMDevice *self, NMSettingCompareFlags compare_flags)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->act_request.obj)
return FALSE;
return nm_active_connection_has_unmodified_applied_connection(
(NMActiveConnection *) priv->act_request.obj,
compare_flags);
}
gpointer
nm_device_get_applied_setting(NMDevice *self, GType setting_type)
{
NMConnection *connection;
connection = nm_device_get_applied_connection(self);
return connection ? nm_connection_get_setting(connection, setting_type) : NULL;
}
NMRfkillType
nm_device_get_rfkill_type(NMDevice *self)
{
NMRfkillType t;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
t = NM_DEVICE_GET_CLASS(self)->rfkill_type;
nm_assert(NM_IN_SET(t, NM_RFKILL_TYPE_UNKNOWN, NM_RFKILL_TYPE_WLAN, NM_RFKILL_TYPE_WWAN));
return t;
}
static const char *
nm_device_get_physical_port_id(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->physical_port_id;
}
/*****************************************************************************/
typedef enum {
CONCHECK_SCHEDULE_UPDATE_INTERVAL,
CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART,
CONCHECK_SCHEDULE_CHECK_EXTERNAL,
CONCHECK_SCHEDULE_CHECK_PERIODIC,
CONCHECK_SCHEDULE_RETURNED_MIN,
CONCHECK_SCHEDULE_RETURNED_BUMP,
CONCHECK_SCHEDULE_RETURNED_MAX,
} ConcheckScheduleMode;
static NMDeviceConnectivityHandle *concheck_start(NMDevice *self,
int addr_family,
NMDeviceConnectivityCallback callback,
gpointer user_data,
gboolean is_periodic);
static void
concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckScheduleMode mode);
static gboolean
_concheck_periodic_timeout_cb(NMDevice *self, int addr_family)
{
_LOGt(LOGD_CONCHECK,
"connectivity: [IPv%c] periodic timeout",
nm_utils_addr_family_to_char(addr_family));
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_PERIODIC);
return G_SOURCE_REMOVE;
}
static gboolean
concheck_ip4_periodic_timeout_cb(gpointer user_data)
{
return _concheck_periodic_timeout_cb(user_data, AF_INET);
}
static gboolean
concheck_ip6_periodic_timeout_cb(gpointer user_data)
{
return _concheck_periodic_timeout_cb(user_data, AF_INET6);
}
static gboolean
concheck_is_possible(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!nm_device_is_real(self) || is_loopback(self))
return FALSE;
/* we enable periodic checks for every device state (except UNKNOWN). Especially with
* unmanaged devices, it is interesting to know whether we have connectivity on that device. */
if (priv->state == NM_DEVICE_STATE_UNKNOWN)
return FALSE;
return TRUE;
}
static gboolean
concheck_periodic_schedule_do(NMDevice *self, int addr_family, gint64 now_ns)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean periodic_check_disabled = FALSE;
gint64 expiry, tdiff;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
/* we always cancel whatever was pending. */
if (nm_clear_g_source(&priv->concheck_x[IS_IPv4].p_cur_id))
periodic_check_disabled = TRUE;
if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
/* periodic checks are disabled */
goto out;
}
if (!concheck_is_possible(self))
goto out;
nm_assert(now_ns > 0);
nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0);
/* we schedule the timeout based on our current settings cur-interval and cur-basetime.
* Before calling concheck_periodic_schedule_do(), make sure that these properties are
* correct. */
expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns
+ (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
tdiff = expiry - now_ns;
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] periodic-check: %sscheduled in %lld milliseconds (%u seconds "
"interval)",
nm_utils_addr_family_to_char(addr_family),
periodic_check_disabled ? "re-" : "",
(long long) (tdiff / NM_UTILS_NSEC_PER_MSEC),
priv->concheck_x[IS_IPv4].p_cur_interval);
priv->concheck_x[IS_IPv4].p_cur_id =
g_timeout_add(NM_MAX((gint64) 0, tdiff) / NM_UTILS_NSEC_PER_MSEC,
IS_IPv4 ? concheck_ip4_periodic_timeout_cb : concheck_ip6_periodic_timeout_cb,
self);
return TRUE;
out:
if (periodic_check_disabled) {
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] periodic-check: unscheduled",
nm_utils_addr_family_to_char(addr_family));
}
return FALSE;
}
#define CONCHECK_P_PROBE_INTERVAL 1
static void
concheck_periodic_schedule_set(NMDevice *self, int addr_family, ConcheckScheduleMode mode)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gint64 new_expiry, exp_expiry, cur_expiry, tdiff;
gint64 now_ns = 0;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->concheck_x[IS_IPv4].p_max_interval == 0) {
/* periodic check is disabled. Nothing to do. */
return;
}
if (!priv->concheck_x[IS_IPv4].p_cur_id) {
/* we currently don't have a timeout scheduled. No need to reschedule
* another one... */
if (NM_IN_SET(mode,
CONCHECK_SCHEDULE_UPDATE_INTERVAL,
CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART)) {
/* ... unless, we are about to start periodic checks after update-interval.
* In this case, fall through and restart the periodic checks below. */
mode = CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART;
} else
return;
}
switch (mode) {
case CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART:
priv->concheck_x[IS_IPv4].p_cur_interval =
NM_MIN(priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
priv->concheck_x[IS_IPv4].p_cur_basetime_ns =
nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns);
if (concheck_periodic_schedule_do(self, addr_family, now_ns))
concheck_start(self, addr_family, NULL, NULL, TRUE);
return;
case CONCHECK_SCHEDULE_UPDATE_INTERVAL:
/* called with "UPDATE_INTERVAL" and already have a p_cur_id scheduled. */
nm_assert(priv->concheck_x[IS_IPv4].p_max_interval > 0);
nm_assert(priv->concheck_x[IS_IPv4].p_cur_interval > 0);
if (priv->concheck_x[IS_IPv4].p_cur_interval <= priv->concheck_x[IS_IPv4].p_max_interval) {
/* we currently have a shorter interval set, than what we now have. Either,
* because we are probing, or because the previous max interval was shorter.
*
* Either way, the current timer is set just fine. Nothing to do, we will
* probe our way up. */
return;
}
cur_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns
+ (priv->concheck_x[IS_IPv4].p_max_interval * NM_UTILS_NSEC_PER_SEC);
nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns);
priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval;
if (cur_expiry <= now_ns) {
/* Since the last time we scheduled a periodic check, already more than the
* new max_interval passed. We need to start a check right away (and
* schedule a timeout in cur-interval in the future). */
priv->concheck_x[IS_IPv4].p_cur_basetime_ns = now_ns;
if (concheck_periodic_schedule_do(self, addr_family, now_ns))
concheck_start(self, addr_family, NULL, NULL, TRUE);
} else {
/* we are reducing the max-interval to a shorter interval that we have currently
* scheduled (with cur_interval).
*
* However, since the last time we scheduled the check, not even the new max-interval
* expired. All we need to do, is reschedule the timer to expire sooner. The cur_basetime
* is unchanged. */
concheck_periodic_schedule_do(self, addr_family, now_ns);
}
return;
case CONCHECK_SCHEDULE_CHECK_EXTERNAL:
/* a external connectivity check delays our periodic check. We reset the counter. */
priv->concheck_x[IS_IPv4].p_cur_basetime_ns =
nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns);
concheck_periodic_schedule_do(self, addr_family, now_ns);
return;
case CONCHECK_SCHEDULE_CHECK_PERIODIC:
{
gboolean any_periodic_pending;
NMDeviceConnectivityHandle *handle;
guint old_interval = priv->concheck_x[IS_IPv4].p_cur_interval;
any_periodic_pending = FALSE;
c_list_for_each_entry (handle, &priv->concheck_lst_head, concheck_lst) {
if (handle->addr_family != addr_family)
continue;
if (handle->is_periodic_bump) {
handle->is_periodic_bump = FALSE;
handle->is_periodic_bump_on_complete = FALSE;
any_periodic_pending = TRUE;
}
}
if (any_periodic_pending) {
/* we reached a timeout to schedule a new periodic request, however we still
* have period requests pending that didn't complete yet. We need to bump the
* interval already. */
priv->concheck_x[IS_IPv4].p_cur_interval =
NM_MIN(old_interval * 2, priv->concheck_x[IS_IPv4].p_max_interval);
}
/* we just reached a timeout. The expected expiry (exp_expiry) should be
* pretty close to now_ns.
*
* We want to reschedule the timeout at exp_expiry (aka now) + cur_interval. */
nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns);
exp_expiry =
priv->concheck_x[IS_IPv4].p_cur_basetime_ns + (old_interval * NM_UTILS_NSEC_PER_SEC);
new_expiry =
exp_expiry + (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
tdiff = NM_MAX(new_expiry - now_ns, 0);
priv->concheck_x[IS_IPv4].p_cur_basetime_ns =
(now_ns + tdiff) - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
if (concheck_periodic_schedule_do(self, addr_family, now_ns)) {
handle = concheck_start(self, addr_family, NULL, NULL, TRUE);
if (old_interval != priv->concheck_x[IS_IPv4].p_cur_interval) {
/* we just bumped the interval already when scheduling this check.
* When the handle returns, don't bump a second time.
*
* But if we reach the timeout again before the handle returns (this
* code here) we will still bump the interval. */
handle->is_periodic_bump_on_complete = FALSE;
}
}
return;
}
/* we just got an event that we lost connectivity (that is, concheck returned). We reset
* the interval to min/max or increase the probe interval (bump). */
case CONCHECK_SCHEDULE_RETURNED_MIN:
priv->concheck_x[IS_IPv4].p_cur_interval =
NM_MIN(priv->concheck_x[IS_IPv4].p_max_interval, CONCHECK_P_PROBE_INTERVAL);
break;
case CONCHECK_SCHEDULE_RETURNED_MAX:
priv->concheck_x[IS_IPv4].p_cur_interval = priv->concheck_x[IS_IPv4].p_max_interval;
break;
case CONCHECK_SCHEDULE_RETURNED_BUMP:
priv->concheck_x[IS_IPv4].p_cur_interval =
NM_MIN(priv->concheck_x[IS_IPv4].p_cur_interval * 2,
priv->concheck_x[IS_IPv4].p_max_interval);
break;
}
/* we are here, because we returned from a connectivity check and adjust the current interval.
*
* But note that we calculate the new timeout based on the time when we scheduled the
* last check, instead of counting from now. The reason is that we want that the times
* when we schedule checks be at precise intervals, without including the time it took for
* the connectivity check. */
new_expiry = priv->concheck_x[IS_IPv4].p_cur_basetime_ns
+ (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
tdiff = NM_MAX(new_expiry - nm_utils_get_monotonic_timestamp_nsec_cached(&now_ns), 0);
priv->concheck_x[IS_IPv4].p_cur_basetime_ns =
now_ns + tdiff - (priv->concheck_x[IS_IPv4].p_cur_interval * NM_UTILS_NSEC_PER_SEC);
concheck_periodic_schedule_do(self, addr_family, now_ns);
}
static void
concheck_update_interval(NMDevice *self, int addr_family, gboolean check_now)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint new_interval;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
new_interval = nm_connectivity_get_interval(concheck_get_mgr(self));
new_interval = NM_MIN(new_interval, 7 * 24 * 3600);
if (new_interval != priv->concheck_x[IS_IPv4].p_max_interval) {
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] periodic-check: set interval to %u seconds",
nm_utils_addr_family_to_char(addr_family),
new_interval);
priv->concheck_x[IS_IPv4].p_max_interval = new_interval;
}
if (!new_interval) {
/* this will cancel any potentially pending timeout because max-interval is zero.
* But it logs a nice message... */
concheck_periodic_schedule_do(self, addr_family, 0);
/* also update the fake connectivity state. */
concheck_update_state(self, addr_family, NM_CONNECTIVITY_FAKE, TRUE);
return;
}
concheck_periodic_schedule_set(self,
addr_family,
check_now ? CONCHECK_SCHEDULE_UPDATE_INTERVAL_RESTART
: CONCHECK_SCHEDULE_UPDATE_INTERVAL);
}
void
nm_device_check_connectivity_update_interval(NMDevice *self)
{
concheck_update_interval(self, AF_INET, TRUE);
concheck_update_interval(self, AF_INET6, TRUE);
}
static void
concheck_update_state(NMDevice *self,
int addr_family,
NMConnectivityState state,
gboolean allow_periodic_bump)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
/* @state is a result of the connectivity check. We only expect a precise
* number of possible values. */
nm_assert(NM_IN_SET(state,
NM_CONNECTIVITY_LIMITED,
NM_CONNECTIVITY_PORTAL,
NM_CONNECTIVITY_FULL,
NM_CONNECTIVITY_FAKE,
NM_CONNECTIVITY_NONE,
NM_CONNECTIVITY_ERROR));
if (state == NM_CONNECTIVITY_ERROR) {
/* on error, we don't change the current connectivity state,
* except making UNKNOWN to NONE. */
state = priv->concheck_x[IS_IPv4].state;
if (state == NM_CONNECTIVITY_UNKNOWN)
state = NM_CONNECTIVITY_NONE;
} else if (state == NM_CONNECTIVITY_FAKE) {
/* If the connectivity check is disabled and we obtain a fake
* result, make an optimistic guess. */
if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
/* FIXME: the fake connectivity state depends on the availability of
* a default route. However, we have no mechanism that rechecks the
* value if a device route appears/disappears after the device
* was activated. */
if (nm_device_get_best_default_route(self, AF_UNSPEC))
state = NM_CONNECTIVITY_FULL;
else
state = NM_CONNECTIVITY_LIMITED;
} else
state = NM_CONNECTIVITY_NONE;
}
if (priv->concheck_x[IS_IPv4].state == state) {
/* we got a connectivity update, but the state didn't change. If we were probing,
* we bump the probe frequency. */
if (allow_periodic_bump)
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
return;
}
/* we need to update the probe interval before emitting signals. Emitting
* a signal might call back into NMDevice and change the probe settings.
* So, do that first. */
if (state == NM_CONNECTIVITY_FULL) {
/* we reached full connectivity state. Stop probing by setting the
* interval to the max. */
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_MAX);
} else if (priv->concheck_x[IS_IPv4].state == NM_CONNECTIVITY_FULL) {
/* we are about to loose connectivity. (re)start probing by setting
* the timeout interval to the min. */
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_MIN);
} else {
if (allow_periodic_bump)
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_RETURNED_BUMP);
}
_LOGD(LOGD_CONCHECK,
"connectivity state changed from %s to %s",
nm_connectivity_state_to_string(priv->concheck_x[IS_IPv4].state),
nm_connectivity_state_to_string(state));
priv->concheck_x[IS_IPv4].state = state;
_notify(self, IS_IPv4 ? PROP_IP4_CONNECTIVITY : PROP_IP6_CONNECTIVITY);
if (priv->state == NM_DEVICE_STATE_ACTIVATED && !nm_device_sys_iface_state_is_external(self))
_dev_l3_register_l3cds(self, priv->l3cfg, TRUE, NM_TERNARY_DEFAULT);
}
static const char *
nm_device_get_effective_ip_config_method(NMDevice *self, int addr_family)
{
NMDeviceClass *klass;
NMConnection *connection = nm_device_get_applied_connection(self);
const char *method;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_val_if_fail(NM_IS_CONNECTION(connection), "" /* bogus */);
method = nm_utils_get_ip_config_method(connection, addr_family);
if ((IS_IPv4 && nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
|| (!IS_IPv4 && nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))) {
klass = NM_DEVICE_GET_CLASS(self);
if (klass->get_auto_ip_config_method) {
const char *auto_method;
auto_method = klass->get_auto_ip_config_method(self, addr_family);
if (auto_method)
return auto_method;
}
}
return method;
}
static void
concheck_handle_complete(NMDeviceConnectivityHandle *handle, GError *error)
{
const int IS_IPv4 = NM_IS_IPv4(handle->addr_family);
/* The moment we invoke the callback, we unlink it. It signals
* that @handle is handled -- as far as the callee of callback
* is concerned. */
c_list_unlink(&handle->concheck_lst);
if (handle->c_handle)
nm_connectivity_check_cancel(handle->c_handle);
if (handle->callback) {
handle->callback(handle->self,
handle,
NM_DEVICE_GET_PRIVATE(handle->self)->concheck_x[IS_IPv4].state,
error,
handle->user_data);
}
g_slice_free(NMDeviceConnectivityHandle, handle);
}
static void
concheck_cb(NMConnectivity *connectivity,
NMConnectivityCheckHandle *c_handle,
NMConnectivityState state,
gpointer user_data)
{
_nm_unused gs_unref_object NMDevice *self_keep_alive = NULL;
NMDevice *self;
NMDevicePrivate *priv;
NMDeviceConnectivityHandle *handle;
NMDeviceConnectivityHandle *other_handle;
gboolean handle_is_alive;
gboolean allow_periodic_bump;
gboolean any_periodic_before;
gboolean any_periodic_after;
guint64 seq;
handle = user_data;
nm_assert(handle->c_handle == c_handle);
nm_assert(NM_IS_DEVICE(handle->self));
handle->c_handle = NULL;
self = handle->self;
if (state == NM_CONNECTIVITY_CANCELLED) {
/* the only place where we nm_connectivity_check_cancel(@c_handle), is
* from inside concheck_handle_complete(). This is a recursive call,
* nothing to do. */
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] complete check (seq:%llu, cancelled)",
nm_utils_addr_family_to_char(handle->addr_family),
(long long unsigned) handle->seq);
return;
}
/* we keep NMConnectivity instance alive. It cannot be disposing. */
nm_assert(state != NM_CONNECTIVITY_DISPOSING);
self_keep_alive = g_object_ref(self);
/* keep @self alive, while we invoke callbacks. */
priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(handle && c_list_contains(&priv->concheck_lst_head, &handle->concheck_lst));
seq = handle->seq;
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] complete check (seq:%llu, state:%s)",
nm_utils_addr_family_to_char(handle->addr_family),
(long long unsigned) handle->seq,
nm_connectivity_state_to_string(state));
/* find out, if there are any periodic checks pending (either whether they
* were scheduled before or after @handle. */
any_periodic_before = FALSE;
any_periodic_after = FALSE;
c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
if (other_handle->addr_family != handle->addr_family)
continue;
if (other_handle->is_periodic_bump_on_complete) {
if (other_handle->seq < seq)
any_periodic_before = TRUE;
else if (other_handle->seq > seq)
any_periodic_after = TRUE;
}
}
if (NM_IN_SET(state, NM_CONNECTIVITY_ERROR)) {
/* the request failed. We consider this periodic check only as completed if
* this was a periodic check, and there are not checks pending (either
* before or after this one).
*
* We allow_periodic_bump, if the request failed and there are
* still other requests periodic pending. */
allow_periodic_bump =
handle->is_periodic_bump_on_complete && !any_periodic_before && !any_periodic_after;
} else {
/* the request succeeded. This marks the completion of a periodic check,
* if this handle was periodic, or any previously scheduled one (that
* we are going to complete below). */
allow_periodic_bump = handle->is_periodic_bump_on_complete || any_periodic_before;
}
/* first update the new state, and emit signals. */
concheck_update_state(self, handle->addr_family, state, allow_periodic_bump);
handle_is_alive = FALSE;
/* we might have invoked callbacks during concheck_update_state(). The caller might have
* cancelled and thus destroyed @handle. We have to check whether handle is still alive,
* by searching it in the list of alive handles.
*
* Also, we might want to complete all pending callbacks that were started before
* @handle, as they are automatically obsoleted. */
check_handles:
c_list_for_each_entry (other_handle, &priv->concheck_lst_head, concheck_lst) {
if (other_handle->addr_family != handle->addr_family)
continue;
if (other_handle->seq >= seq) {
/* it's not guaranteed that @handle is still in the list. It might already
* be canceled while invoking callbacks for a previous other_handle.
* If it is already cancelled, @handle is a dangling pointer.
*
* Since @seq is assigned uniquely and increasing, either @other_handle is
* @handle (and thus, handle is alive), or it isn't. */
if (other_handle == handle)
handle_is_alive = TRUE;
break;
}
nm_assert(other_handle != handle);
if (!NM_IN_SET(state, NM_CONNECTIVITY_ERROR)) {
/* we also want to complete handles that were started before the current
* @handle. Their response is out-dated. */
concheck_handle_complete(other_handle, NULL);
/* we invoked callbacks, other handles might be cancelled and removed from the list.
* Need to iterate the list from the start. */
goto check_handles;
}
}
if (!handle_is_alive) {
/* We didn't find @handle in the list of alive handles. Thus, the handles
* was cancelled while we were invoking events. Nothing to do, and don't
* touch the dangling pointer. */
return;
}
concheck_handle_complete(handle, NULL);
}
static NMDeviceConnectivityHandle *
concheck_start(NMDevice *self,
int addr_family,
NMDeviceConnectivityCallback callback,
gpointer user_data,
gboolean is_periodic)
{
static guint64 seq_counter = 0;
NMDevicePrivate *priv;
NMDeviceConnectivityHandle *handle;
const char *ifname;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
handle = g_slice_new0(NMDeviceConnectivityHandle);
handle->seq = ++seq_counter;
handle->self = self;
handle->callback = callback;
handle->user_data = user_data;
handle->is_periodic = is_periodic;
handle->is_periodic_bump = is_periodic;
handle->is_periodic_bump_on_complete = is_periodic;
handle->addr_family = addr_family;
c_list_link_tail(&priv->concheck_lst_head, &handle->concheck_lst);
_LOGT(LOGD_CONCHECK,
"connectivity: [IPv%c] start check (seq:%llu%s)",
nm_utils_addr_family_to_char(addr_family),
(long long unsigned) handle->seq,
is_periodic ? ", periodic-check" : "");
if (NM_IS_IPv4(addr_family) && !priv->concheck_rp_filter_checked) {
if ((ifname = nm_device_get_ip_iface_from_platform(self))) {
gboolean due_to_all;
int val;
val = nm_platform_sysctl_ip_conf_get_rp_filter_ipv4(nm_device_get_platform(self),
ifname,
TRUE,
&due_to_all);
if (val == 1) {
_LOGW(LOGD_CONCHECK,
"connectivity: \"/proc/sys/net/ipv4/conf/%s/rp_filter\" is set to \"1\". "
"This might break connectivity checking for IPv4 on this device",
due_to_all ? "all" : ifname);
}
}
/* we only check once per device. It's a warning after all. */
priv->concheck_rp_filter_checked = TRUE;
}
handle->c_handle = nm_connectivity_check_start(concheck_get_mgr(self),
handle->addr_family,
nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
nm_device_get_ip_iface(self),
concheck_cb,
handle);
return handle;
}
NMDeviceConnectivityHandle *
nm_device_check_connectivity(NMDevice *self,
int addr_family,
NMDeviceConnectivityCallback callback,
gpointer user_data)
{
if (!concheck_is_possible(self))
return NULL;
concheck_periodic_schedule_set(self, addr_family, CONCHECK_SCHEDULE_CHECK_EXTERNAL);
return concheck_start(self, addr_family, callback, user_data, FALSE);
}
void
nm_device_check_connectivity_cancel(NMDeviceConnectivityHandle *handle)
{
gs_free_error GError *cancelled_error = NULL;
g_return_if_fail(handle);
g_return_if_fail(NM_IS_DEVICE(handle->self));
g_return_if_fail(!c_list_is_empty(&handle->concheck_lst));
/* nobody has access to periodic handles, and cannot cancel
* them externally. */
nm_assert(!handle->is_periodic);
nm_utils_error_set_cancelled(&cancelled_error, FALSE, "NMDevice");
concheck_handle_complete(handle, cancelled_error);
}
NMConnectivityState
nm_device_get_connectivity_state(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), NM_CONNECTIVITY_UNKNOWN);
priv = NM_DEVICE_GET_PRIVATE(self);
switch (addr_family) {
case AF_INET:
case AF_INET6:
return priv->concheck_x[NM_IS_IPv4(addr_family)].state;
default:
nm_assert(addr_family == AF_UNSPEC);
return NM_MAX_WITH_CMP(nm_connectivity_state_cmp,
priv->concheck_x[0].state,
priv->concheck_x[1].state);
}
}
/*****************************************************************************/
static SlaveInfo *
find_slave_info(NMDevice *self, NMDevice *slave)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
CList *iter;
SlaveInfo *info;
c_list_for_each (iter, &priv->slaves) {
info = c_list_entry(iter, SlaveInfo, lst_slave);
if (info->slave == slave)
return info;
}
return NULL;
}
/**
* nm_device_master_enslave_slave:
* @self: the master device
* @slave: the slave device to enslave
* @connection: (allow-none): the slave device's connection
*
* If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
* etc) then this function enslaves @slave.
*
* Returns: %TRUE on success, %FALSE on failure or if this device cannot enslave
* other devices.
*/
static gboolean
nm_device_master_enslave_slave(NMDevice *self, NMDevice *slave, NMConnection *connection)
{
SlaveInfo *info;
gboolean success = FALSE;
gboolean configure;
g_return_val_if_fail(self != NULL, FALSE);
g_return_val_if_fail(slave != NULL, FALSE);
g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE);
info = find_slave_info(self, slave);
if (!info)
return FALSE;
if (info->slave_is_enslaved)
success = TRUE;
else {
configure = (info->configure && connection != NULL);
if (configure)
g_return_val_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
success = NM_DEVICE_GET_CLASS(self)->enslave_slave(self, slave, connection, configure);
info->slave_is_enslaved = success;
}
nm_device_slave_notify_enslave(info->slave, success);
/* Ensure the device's hardware address is up-to-date; it often changes
* when slaves change.
*/
nm_device_update_hw_address(self);
/* Since slave devices don't have their own IP configuration,
* set the MTU here.
*/
_commit_mtu(slave);
/* Restart IP configuration if we're waiting for slaves. Do this
* after updating the hardware address as IP config may need the
* new address.
*/
if (success)
nm_device_activate_schedule_stage3_ip_config(self, FALSE);
return success;
}
/**
* nm_device_master_release_one_slave:
* @self: the master device
* @slave: the slave device to release
* @configure: whether @self needs to actually release @slave
* @force: force the release of @slave even if it wasn't added
* to @master by NetworkManager
* @reason: the state change reason for the @slave
*
* If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
* etc) then this function releases the previously enslaved @slave and/or
* updates the state of @self and @slave to reflect its release.
*/
static void
nm_device_master_release_one_slave(NMDevice *self,
NMDevice *slave,
gboolean configure,
gboolean force,
NMDeviceStateReason reason)
{
NMDevicePrivate *priv;
NMDevicePrivate *slave_priv;
SlaveInfo *info;
gs_unref_object NMDevice *self_free = NULL;
g_return_if_fail(NM_DEVICE(self));
g_return_if_fail(NM_DEVICE(slave));
g_return_if_fail(!force || configure);
g_return_if_fail(NM_DEVICE_GET_CLASS(self)->release_slave != NULL);
info = find_slave_info(self, slave);
_LOGT(LOGD_CORE,
"master: release one slave %p/%s %s%s",
slave,
nm_device_get_iface(slave),
!info ? "(not registered)" : (info->slave_is_enslaved ? "(enslaved)" : "(not enslaved)"),
force ? " (force-configure)" : (configure ? " (configure)" : ""));
if (!info)
g_return_if_reached();
priv = NM_DEVICE_GET_PRIVATE(self);
slave_priv = NM_DEVICE_GET_PRIVATE(slave);
g_return_if_fail(self == slave_priv->master);
nm_assert(slave == info->slave);
/* first, let subclasses handle the release ... */
if (info->slave_is_enslaved || nm_device_sys_iface_state_is_external(slave) || force)
NM_DEVICE_GET_CLASS(self)->release_slave(self, slave, configure);
/* raise notifications about the release, including clearing is_enslaved. */
nm_device_slave_notify_release(slave, reason);
/* keep both alive until the end of the function.
* Transfers ownership from slave_priv->master. */
self_free = self;
c_list_unlink(&info->lst_slave);
slave_priv->master = NULL;
g_signal_handler_disconnect(slave, info->watch_id);
g_object_unref(slave);
g_slice_free(SlaveInfo, info);
if (c_list_is_empty(&priv->slaves)) {
_active_connection_set_state_flags_full(self,
0,
NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
}
/* Ensure the device's hardware address is up-to-date; it often changes
* when slaves change.
*/
nm_device_update_hw_address(self);
nm_device_set_unmanaged_by_flags(slave,
NM_UNMANAGED_IS_SLAVE,
NM_UNMAN_FLAG_OP_FORGET,
NM_DEVICE_STATE_REASON_REMOVED);
}
/*****************************************************************************/
/**
* can_unmanaged_external_down:
* @self: the device
*
* Check whether the device should stay NM_UNMANAGED_EXTERNAL_DOWN unless
* IFF_UP-ed externally.
*/
static gboolean
can_unmanaged_external_down(NMDevice *self)
{
return !NM_DEVICE_GET_PRIVATE(self)->nm_owned && nm_device_is_software(self);
}
static NMUnmanFlagOp
_dev_unmanaged_is_external_down(NMDevice *self, gboolean consider_can)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (consider_can && !NM_DEVICE_GET_CLASS(self)->can_unmanaged_external_down(self))
return NM_UNMAN_FLAG_OP_FORGET;
/* Manage externally-created software interfaces only when they are IFF_UP */
if (priv->ifindex <= 0 || !priv->up
|| !(!c_list_is_empty(&priv->slaves)
|| nm_platform_link_can_assume(nm_device_get_platform(self), priv->ifindex)))
return NM_UNMAN_FLAG_OP_SET_UNMANAGED;
return NM_UNMAN_FLAG_OP_SET_MANAGED;
}
static void
_dev_unmanaged_check_external_down(NMDevice *self, gboolean only_if_unmanaged, gboolean now)
{
NMUnmanFlagOp ext_flags;
if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN))
return;
if (only_if_unmanaged) {
if (!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN))
return;
}
ext_flags = _dev_unmanaged_is_external_down(self, FALSE);
if (ext_flags != NM_UNMAN_FLAG_OP_SET_UNMANAGED) {
/* Ensure the assume check is queued before any queued state changes
* from the transition to UNAVAILABLE.
*/
nm_device_queue_recheck_assume(self);
}
if (now) {
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_EXTERNAL_DOWN,
ext_flags,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
} else {
nm_device_set_unmanaged_by_flags_queue(self,
NM_UNMANAGED_EXTERNAL_DOWN,
ext_flags,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
}
void
nm_device_update_dynamic_ip_setup(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->state < NM_DEVICE_STATE_IP_CONFIG || priv->state > NM_DEVICE_STATE_ACTIVATED)
return;
g_hash_table_remove_all(priv->ip6_saved_properties);
if (priv->ipdhcp_data_4.state != NM_DEVICE_IP_STATE_NONE)
_dev_ipdhcpx_restart(self, AF_INET, FALSE);
if (priv->ipdhcp_data_6.state != NM_DEVICE_IP_STATE_NONE)
_dev_ipdhcpx_restart(self, AF_INET6, FALSE);
if (priv->ipac6_data.ndisc) {
/* FIXME: todo */
}
if (priv->ipshared_data_4.v4.dnsmasq_manager) {
/* FIXME: todo */
}
}
/*****************************************************************************/
static void
carrier_changed_notify(NMDevice *self, gboolean carrier)
{
/* stub */
}
static void
carrier_changed(NMDevice *self, gboolean carrier)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->state <= NM_DEVICE_STATE_UNMANAGED)
return;
nm_device_recheck_available_connections(self);
/* ignore-carrier devices ignore all carrier-down events */
if (priv->ignore_carrier && !carrier)
return;
if (nm_device_is_master(self)) {
if (carrier) {
/* Force master to retry getting ip addresses when carrier
* is restored. */
if (priv->state == NM_DEVICE_STATE_ACTIVATED)
nm_device_update_dynamic_ip_setup(self);
/* If needed, also resume IP configuration that is
* waiting for carrier. */
if (priv->state == NM_DEVICE_STATE_IP_CONFIG)
nm_device_activate_schedule_stage3_ip_config(self, FALSE);
return;
}
/* fall-through and change state of device */
} else if (priv->is_enslaved && !carrier) {
/* Slaves don't deactivate when they lose carrier; for
* bonds/teams in particular that would be actively
* counterproductive.
*/
return;
}
if (carrier) {
if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
nm_device_queue_state(self,
NM_DEVICE_STATE_DISCONNECTED,
NM_DEVICE_STATE_REASON_CARRIER);
} else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
/* If the device is already in DISCONNECTED state without a carrier
* (probably because it is tagged for carrier ignore) ensure that
* when the carrier appears, auto connections are rechecked for
* the device.
*/
nm_device_emit_recheck_auto_activate(self);
} else if (priv->state == NM_DEVICE_STATE_ACTIVATED) {
/* If the device is active without a carrier (probably because it is
* tagged for carrier ignore) ensure that when the carrier appears we
* renew DHCP leases and such.
*/
nm_device_update_dynamic_ip_setup(self);
}
} else {
if (priv->state == NM_DEVICE_STATE_UNAVAILABLE) {
if (priv->queued_state.id && priv->queued_state.state >= NM_DEVICE_STATE_DISCONNECTED)
queued_state_clear(self);
} else {
nm_device_queue_state(self,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_CARRIER);
}
}
}
static gboolean
carrier_disconnected_action_cb(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_LOGD(LOGD_DEVICE,
"carrier: link disconnected (calling deferred action) (id=%u)",
priv->carrier_defer_id);
priv->carrier_defer_id = 0;
carrier_changed(self, FALSE);
return FALSE;
}
static void
carrier_disconnected_action_cancel(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint id = priv->carrier_defer_id;
if (nm_clear_g_source(&priv->carrier_defer_id)) {
_LOGD(LOGD_DEVICE, "carrier: link disconnected (canceling deferred action) (id=%u)", id);
}
}
void
nm_device_set_carrier(NMDevice *self, gboolean carrier)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceState state = nm_device_get_state(self);
gboolean notify_flags = FALSE;
if (priv->carrier == carrier)
return;
if (NM_FLAGS_ALL(priv->capabilities,
NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
notify_flags = set_interface_flags(self, NM_DEVICE_INTERFACE_FLAG_CARRIER, carrier, FALSE);
}
priv->carrier = carrier;
nm_gobject_notify_together(self, PROP_CARRIER, notify_flags ? PROP_INTERFACE_FLAGS : PROP_0);
if (priv->carrier) {
_LOGI(LOGD_DEVICE, "carrier: link connected");
carrier_disconnected_action_cancel(self);
NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier);
carrier_changed(self, TRUE);
if (priv->carrier_wait_id) {
nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
_carrier_wait_check_queued_act_request(self);
}
} else {
if (priv->carrier_wait_id)
nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
NM_DEVICE_GET_CLASS(self)->carrier_changed_notify(self, carrier);
if (state <= NM_DEVICE_STATE_DISCONNECTED && !priv->queued_act_request) {
_LOGD(LOGD_DEVICE, "carrier: link disconnected");
carrier_changed(self, FALSE);
} else {
gint64 now_ms, until_ms;
now_ms = nm_utils_get_monotonic_timestamp_msec();
until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
priv->carrier_defer_id =
g_timeout_add(until_ms - now_ms, carrier_disconnected_action_cb, self);
_LOGD(LOGD_DEVICE,
"carrier: link disconnected (deferring action for %ld milliseconds) (id=%u)",
(long) (until_ms - now_ms),
priv->carrier_defer_id);
}
}
}
static void
nm_device_set_carrier_from_platform(NMDevice *self)
{
int ifindex;
if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)) {
if (!nm_device_has_capability(self, NM_DEVICE_CAP_NONSTANDARD_CARRIER)
&& (ifindex = nm_device_get_ip_ifindex(self)) > 0) {
nm_device_set_carrier(
self,
nm_platform_link_is_connected(nm_device_get_platform(self), ifindex));
}
} else {
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
/* Fake online link when carrier detection is not available. */
if (!priv->carrier) {
priv->carrier = TRUE;
_notify(self, PROP_CARRIER);
}
}
}
/*****************************************************************************/
static void
device_recheck_slave_status(NMDevice *self, const NMPlatformLink *plink)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDevice *master;
nm_auto_nmpobj const NMPObject *plink_master_keep_alive = NULL;
const NMPlatformLink *plink_master;
g_return_if_fail(plink);
if (plink->master <= 0)
goto out;
master = nm_manager_get_device_by_ifindex(NM_MANAGER_GET, plink->master);
plink_master = nm_platform_link_get(nm_device_get_platform(self), plink->master);
plink_master_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink_master));
if (master == NULL && plink_master && nm_streq0(plink_master->name, "ovs-system")
&& plink_master->type == NM_LINK_TYPE_OPENVSWITCH) {
_LOGD(LOGD_DEVICE, "the device claimed by openvswitch");
goto out;
}
priv->master_ifindex = plink->master;
if (priv->master) {
if (plink->master > 0 && plink->master == nm_device_get_ifindex(priv->master)) {
/* call add-slave again. We expect @self already to be added to
* the master, but this also triggers a recheck-assume. */
nm_device_master_add_slave(priv->master, self, FALSE);
goto out;
}
nm_device_master_release_one_slave(priv->master,
self,
FALSE,
FALSE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
if (master && NM_DEVICE_GET_CLASS(master)->enslave_slave) {
nm_device_master_add_slave(master, self, FALSE);
goto out;
}
if (master) {
_LOGD(LOGD_DEVICE,
"enslaved to non-master-type device %s; ignoring",
nm_device_get_iface(master));
} else {
_LOGD(LOGD_DEVICE,
"enslaved to unknown device %d (%s%s%s)",
plink->master,
NM_PRINT_FMT_QUOTED(plink_master, "\"", plink_master->name, "\"", "??"));
}
if (!priv->ifindex_changed_id) {
priv->ifindex_changed_id = g_signal_connect(nm_device_get_manager(self),
NM_MANAGER_DEVICE_IFINDEX_CHANGED,
G_CALLBACK(device_ifindex_changed_cb),
self);
}
return;
out:
nm_clear_g_signal_handler(nm_device_get_manager(self), &priv->ifindex_changed_id);
}
static void
device_ifindex_changed_cb(NMManager *manager, NMDevice *device_changed, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->master_ifindex != nm_device_get_ifindex(device_changed))
return;
_LOGD(LOGD_DEVICE,
"master %s with ifindex %d appeared",
nm_device_get_iface(device_changed),
nm_device_get_ifindex(device_changed));
if (!priv->device_link_changed_id)
priv->device_link_changed_id = g_idle_add((GSourceFunc) device_link_changed, self);
}
static void
device_update_interface_flags(NMDevice *self, const NMPlatformLink *plink)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceInterfaceFlags flags = NM_DEVICE_INTERFACE_FLAG_NONE;
if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP))
flags |= NM_DEVICE_INTERFACE_FLAG_UP;
if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP))
flags |= NM_DEVICE_INTERFACE_FLAG_LOWER_UP;
if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_PROMISC))
flags |= NM_DEVICE_INTERFACE_FLAG_PROMISC;
if (NM_FLAGS_ALL(priv->capabilities,
NM_DEVICE_CAP_CARRIER_DETECT | NM_DEVICE_CAP_NONSTANDARD_CARRIER)) {
if (priv->carrier)
flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
} else {
if (plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP))
flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
}
set_interface_flags_full(self,
NM_DEVICE_INTERFACE_FLAG_UP | NM_DEVICE_INTERFACE_FLAG_LOWER_UP
| NM_DEVICE_INTERFACE_FLAG_CARRIER
| NM_DEVICE_INTERFACE_FLAG_PROMISC,
flags,
TRUE);
}
static gboolean
device_link_changed(gpointer user_data)
{
NMDevice *self = user_data;
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean ip_ifname_changed = FALSE;
nm_auto_nmpobj const NMPObject *pllink_keep_alive = NULL;
const NMPlatformLink *pllink;
const char *str;
int ifindex;
gboolean was_up;
gboolean update_unmanaged_specs = FALSE;
gboolean got_hw_addr = FALSE, had_hw_addr;
gboolean seen_down = priv->device_link_changed_down;
priv->device_link_changed_id = 0;
priv->device_link_changed_down = FALSE;
ifindex = nm_device_get_ifindex(self);
if (ifindex <= 0)
return G_SOURCE_REMOVE;
pllink = nm_platform_link_get(nm_device_get_platform(self), ifindex);
if (!pllink)
return G_SOURCE_REMOVE;
pllink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(pllink));
str = nm_platform_link_get_udi(nm_device_get_platform(self), pllink->ifindex);
if (!nm_streq0(str, priv->udi)) {
g_free(priv->udi);
priv->udi = g_strdup(str);
_notify(self, PROP_UDI);
}
str = nm_platform_link_get_path(nm_device_get_platform(self), pllink->ifindex);
if (!nm_streq0(str, priv->path)) {
g_free(priv->path);
priv->path = g_strdup(str);
_notify(self, PROP_PATH);
}
if (!nm_streq0(pllink->driver, priv->driver)) {
g_free(priv->driver);
priv->driver = g_strdup(pllink->driver);
_notify(self, PROP_DRIVER);
}
_set_mtu(self, pllink->mtu);
if (ifindex == nm_device_get_ip_ifindex(self))
_stats_update_counters_from_pllink(self, pllink);
had_hw_addr = (priv->hw_addr != NULL);
nm_device_update_hw_address(self);
got_hw_addr = (!had_hw_addr && priv->hw_addr);
nm_device_update_permanent_hw_address(self, FALSE);
if (pllink->name[0] && !nm_streq(priv->iface, pllink->name)) {
_LOGI(LOGD_DEVICE,
"interface index %d renamed iface from '%s' to '%s'",
priv->ifindex,
priv->iface,
pllink->name);
g_free(priv->iface_);
priv->iface_ = g_strdup(pllink->name);
/* If the device has no explicit ip_iface, then changing iface changes ip_iface too. */
ip_ifname_changed = !priv->ip_iface;
if (!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
/* Since the interface name changed, we need to re-evaluate the
* user settings specs. */
update_unmanaged_specs = TRUE;
}
_notify(self, PROP_IFACE);
if (ip_ifname_changed)
update_prop_ip_iface(self);
/* Re-match available connections against the new interface name */
nm_device_recheck_available_connections(self);
/* Let any connections that use the new interface name have a chance
* to auto-activate on the device.
*/
nm_device_emit_recheck_auto_activate(self);
}
if (priv->ipac6_data.ndisc && pllink->inet6_token.id) {
if (nm_ndisc_set_iid(priv->ipac6_data.ndisc, pllink->inet6_token, TRUE))
_LOGD(LOGD_DEVICE, "IPv6 tokenized identifier present on device %s", priv->iface);
}
/* Update carrier from link event if applicable. */
if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)
&& !nm_device_has_capability(self, NM_DEVICE_CAP_NONSTANDARD_CARRIER))
nm_device_set_carrier(self, pllink->connected);
device_update_interface_flags(self, pllink);
klass->link_changed(self, pllink);
/* Update DHCP, etc, if needed */
if (ip_ifname_changed)
nm_device_update_dynamic_ip_setup(self);
was_up = priv->up;
priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP);
if (pllink->initialized && nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
NMDeviceStateReason reason;
nm_device_set_unmanaged_by_user_udev(self);
nm_device_set_unmanaged_by_user_conf(self);
reason = NM_DEVICE_STATE_REASON_NOW_MANAGED;
/* If the device is a external-down candidated but no longer has external
* down set, we must clear the platform-unmanaged flag with reason
* "assumed". */
if (nm_device_get_unmanaged_mask(self, NM_UNMANAGED_EXTERNAL_DOWN)
&& !nm_device_get_unmanaged_flags(self, NM_UNMANAGED_EXTERNAL_DOWN)) {
/* actually, user-udev overwrites external-down. So we only assume the device,
* when it is a external-down candidate, which is not managed via udev. */
if (!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_UDEV)) {
/* Ensure the assume check is queued before any queued state changes
* from the transition to UNAVAILABLE.
*/
reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
}
}
/* The assume check should happen before the device transitions to
* UNAVAILABLE, because in UNAVAILABLE we already clean up the IP
* configuration. Therefore, this function should never trigger a
* sync state transition.
*/
nm_device_queue_recheck_assume(self);
nm_device_set_unmanaged_by_flags_queue(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
}
_dev_unmanaged_check_external_down(self, FALSE, FALSE);
device_recheck_slave_status(self, pllink);
if (priv->up && (!was_up || seen_down)) {
/* the link was down and just came up. That happens for example, while changing MTU.
* We must restore IP configuration.
*
* FIXME(l3cfg): when NML3Cfg notices that the device goes down and up, then
* it should automatically schedule a REAPPLY commit -- provided that the current
* commit-type is >= UPDATE. The idea is to move logic away from NMDevice
* so that it theoretically would also work for NMVpnConnection (although,
* NMVpnConnection should become like a regular device, akin to NMDevicePpp).
*/
if (!nm_device_sys_iface_state_is_external(self))
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
}
if (update_unmanaged_specs)
nm_device_set_unmanaged_by_user_settings(self, FALSE);
if (got_hw_addr && !priv->up && nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE) {
/*
* If the device is UNAVAILABLE, any previous try to
* bring it up probably has failed because of the
* invalid hardware address; try again.
*/
nm_device_bring_up(self, TRUE, NULL);
nm_device_queue_recheck_available(self,
NM_DEVICE_STATE_REASON_NONE,
NM_DEVICE_STATE_REASON_NONE);
}
return G_SOURCE_REMOVE;
}
static gboolean
device_ip_link_changed(gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NMPlatformLink *pllink;
const char *ip_iface;
priv->device_ip_link_changed_id = 0;
if (priv->ip_ifindex <= 0)
return G_SOURCE_REMOVE;
nm_assert(priv->ip_iface);
pllink = nm_platform_link_get(nm_device_get_platform(self), priv->ip_ifindex);
if (!pllink)
return G_SOURCE_REMOVE;
if (priv->ifindex <= 0 && pllink->mtu)
_set_mtu(self, pllink->mtu);
_stats_update_counters_from_pllink(self, pllink);
ip_iface = pllink->name;
if (!ip_iface[0])
return FALSE;
if (!nm_streq(priv->ip_iface, ip_iface)) {
_LOGI(LOGD_DEVICE,
"ip-ifname: interface index %d renamed ip_iface (%d) from '%s' to '%s'",
priv->ifindex,
priv->ip_ifindex,
priv->ip_iface,
ip_iface);
g_free(priv->ip_iface_);
priv->ip_iface_ = g_strdup(ip_iface);
update_prop_ip_iface(self);
nm_device_update_dynamic_ip_setup(self);
}
return G_SOURCE_REMOVE;
}
static void
link_changed_cb(NMPlatform *platform,
int obj_type_i,
int ifindex,
NMPlatformLink *info,
int change_type_i,
NMDevice *self)
{
const NMPlatformSignalChangeType change_type = change_type_i;
NMDevicePrivate *priv;
if (change_type != NM_PLATFORM_SIGNAL_CHANGED)
return;
priv = NM_DEVICE_GET_PRIVATE(self);
if (ifindex == nm_device_get_ifindex(self)) {
if (!(info->n_ifi_flags & IFF_UP))
priv->device_link_changed_down = TRUE;
if (!priv->device_link_changed_id) {
priv->device_link_changed_id = g_idle_add(device_link_changed, self);
_LOGD(LOGD_DEVICE, "queued link change for ifindex %d", ifindex);
}
} else if (ifindex == nm_device_get_ip_ifindex(self)) {
if (!priv->device_ip_link_changed_id) {
priv->device_ip_link_changed_id = g_idle_add(device_ip_link_changed, self);
_LOGD(LOGD_DEVICE, "queued link change for ip-ifindex %d", ifindex);
}
}
}
/*****************************************************************************/
static void
link_changed(NMDevice *self, const NMPlatformLink *pllink)
{
/* stub implementation of virtual function to allow subclasses to chain up. */
}
static gboolean
link_type_compatible(NMDevice *self, NMLinkType link_type, gboolean *out_compatible, GError **error)
{
NMDeviceClass *klass;
NMLinkType device_type;
guint i = 0;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
klass = NM_DEVICE_GET_CLASS(self);
if (!klass->link_types) {
NM_SET_OUT(out_compatible, FALSE);
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Device does not support platform links");
return FALSE;
}
device_type = self->_priv->link_type;
if (device_type > NM_LINK_TYPE_UNKNOWN && device_type != link_type) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Needed link type 0x%x does not match the platform link type 0x%X",
device_type,
link_type);
return FALSE;
}
for (i = 0; klass->link_types[i] > NM_LINK_TYPE_UNKNOWN; i++) {
if (klass->link_types[i] == link_type)
return TRUE;
if (klass->link_types[i] == NM_LINK_TYPE_ANY)
return TRUE;
}
NM_SET_OUT(out_compatible, FALSE);
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Device does not support platform link type 0x%X",
link_type);
return FALSE;
}
/**
* nm_device_realize_start():
* @self: the #NMDevice
* @plink: an existing platform link or %NULL
* @assume_state_guess_assume: set the guess_assume state.
* @assume_state_connection_uuid: set the connection uuid to assume.
* @set_nm_owned: for software device, if TRUE set nm-owned.
* @unmanaged_user_explicit: the user-explicit unmanaged flag to apply
* on the device initially.
* @out_compatible: %TRUE on return if @self is compatible with @plink
* @error: location to store error, or %NULL
*
* Initializes and sets up the device using existing backing resources. Before
* the device is ready for use nm_device_realize_finish() must be called.
* @out_compatible will only be set if @plink is not %NULL, and
*
* Important: if nm_device_realize_start() returns %TRUE, the caller MUST
* also call nm_device_realize_finish() to balance g_object_freeze_notify().
*
* Returns: %TRUE on success, %FALSE on error
*/
gboolean
nm_device_realize_start(NMDevice *self,
const NMPlatformLink *plink,
gboolean assume_state_guess_assume,
const char *assume_state_connection_uuid,
gboolean set_nm_owned,
NMUnmanFlagOp unmanaged_user_explicit,
gboolean *out_compatible,
GError **error)
{
nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
nm_assert(!plink || NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK);
NM_SET_OUT(out_compatible, TRUE);
if (plink) {
if (!nm_streq0(nm_device_get_iface(self), plink->name)) {
NM_SET_OUT(out_compatible, FALSE);
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Device interface name does not match platform link");
return FALSE;
}
if (!link_type_compatible(self, plink->type, out_compatible, error))
return FALSE;
plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink));
}
realize_start_setup(self,
plink,
assume_state_guess_assume,
assume_state_connection_uuid,
set_nm_owned,
unmanaged_user_explicit,
FALSE);
return TRUE;
}
/**
* nm_device_create_and_realize():
* @self: the #NMDevice
* @connection: the #NMConnection being activated
* @parent: the parent #NMDevice if any
* @error: location to store error, or %NULL
*
* Creates any backing resources needed to realize the device to proceed
* with activating @connection.
*
* Returns: %TRUE on success, %FALSE on error
*/
gboolean
nm_device_create_and_realize(NMDevice *self,
NMConnection *connection,
NMDevice *parent,
GError **error)
{
nm_auto_nmpobj const NMPObject *plink_keep_alive = NULL;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NMPlatformLink *plink;
gboolean nm_owned;
/* Must be set before device is realized */
plink = nm_platform_link_get_by_ifname(nm_device_get_platform(self), priv->iface);
nm_owned = !plink || !link_type_compatible(self, plink->type, NULL, NULL);
_LOGD(LOGD_DEVICE, "create (is %snm-owned)", nm_owned ? "" : "not ");
plink = NULL;
/* Create any resources the device needs */
if (NM_DEVICE_GET_CLASS(self)->create_and_realize) {
if (!NM_DEVICE_GET_CLASS(self)->create_and_realize(self, connection, parent, &plink, error))
return FALSE;
if (plink) {
nm_assert(NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK);
plink_keep_alive = nmp_object_ref(NMP_OBJECT_UP_CAST(plink));
}
}
priv->nm_owned = nm_owned;
realize_start_setup(self,
plink,
FALSE, /* assume_state_guess_assume */
NULL, /* assume_state_connection_uuid */
FALSE,
NM_UNMAN_FLAG_OP_FORGET,
TRUE);
nm_device_realize_finish(self, plink);
if (nm_device_get_managed(self, FALSE)) {
nm_device_state_changed(self,
NM_DEVICE_STATE_UNAVAILABLE,
NM_DEVICE_STATE_REASON_NOW_MANAGED);
}
return TRUE;
}
static gboolean
can_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink)
{
return TRUE;
}
void
nm_device_update_from_platform_link(NMDevice *self, const NMPlatformLink *plink)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *str;
gboolean ifindex_changed;
guint32 mtu;
if (!NM_DEVICE_GET_CLASS(self)->can_update_from_platform_link(self, plink))
return;
g_return_if_fail(plink == NULL || link_type_compatible(self, plink->type, NULL, NULL));
str = plink ? nm_platform_link_get_udi(nm_device_get_platform(self), plink->ifindex) : NULL;
if (!nm_streq0(str, priv->udi)) {
g_free(priv->udi);
priv->udi = g_strdup(str);
_notify(self, PROP_UDI);
}
str = plink ? nm_platform_link_get_path(nm_device_get_platform(self), plink->ifindex) : NULL;
if (!nm_streq0(str, priv->path)) {
g_free(priv->path);
priv->path = g_strdup(str);
_notify(self, PROP_PATH);
}
if (plink && !nm_str_is_empty(plink->name) && nm_strdup_reset(&priv->iface_, plink->name))
_notify(self, PROP_IFACE);
str = plink ? plink->driver : NULL;
if (!nm_streq0(str, priv->driver)) {
g_free(priv->driver);
priv->driver = g_strdup(str);
_notify(self, PROP_DRIVER);
}
if (plink) {
priv->up = NM_FLAGS_HAS(plink->n_ifi_flags, IFF_UP);
if (plink->ifindex == nm_device_get_ip_ifindex(self))
_stats_update_counters_from_pllink(self, plink);
} else {
priv->up = FALSE;
}
mtu = plink ? plink->mtu : 0;
_set_mtu(self, mtu);
ifindex_changed = _set_ifindex(self, plink ? plink->ifindex : 0, FALSE);
if (ifindex_changed)
NM_DEVICE_GET_CLASS(self)->link_changed(self, plink);
device_update_interface_flags(self, plink);
}
/*****************************************************************************/
static void
sriov_op_start(NMDevice *self, SriovOp *op)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(!priv->sriov.pending);
op->cancellable = g_cancellable_new();
op->device = g_object_ref(self);
priv->sriov.pending = op;
nm_platform_link_set_sriov_params_async(nm_device_get_platform(self),
priv->ifindex,
op->num_vfs,
op->autoprobe,
sriov_op_cb,
op,
op->cancellable);
}
static void
sriov_op_cb(GError *error, gpointer user_data)
{
SriovOp *op = user_data;
gs_unref_object NMDevice *self = op->device;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(op == priv->sriov.pending);
g_clear_object(&op->cancellable);
if (op->callback)
op->callback(error, op->callback_data);
priv->sriov.pending = NULL;
nm_g_slice_free(op);
if (priv->sriov.next) {
sriov_op_start(self, g_steal_pointer(&priv->sriov.next));
}
}
static void
sriov_op_queue_op(NMDevice *self, SriovOp *op)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->sriov.next) {
SriovOp *op_next = g_steal_pointer(&priv->sriov.next);
priv->sriov.next = op;
/* Cancel the next operation immediately */
if (op_next->callback) {
gs_free_error GError *error = NULL;
nm_utils_error_set_cancelled(&error, FALSE, NULL);
op_next->callback(error, op_next->callback_data);
}
nm_g_slice_free(op_next);
return;
}
if (priv->sriov.pending) {
priv->sriov.next = op;
g_cancellable_cancel(priv->sriov.pending->cancellable);
return;
}
if (op)
sriov_op_start(self, op);
}
static void
sriov_op_queue(NMDevice *self,
guint num_vfs,
NMOptionBool autoprobe,
NMPlatformAsyncCallback callback,
gpointer callback_data)
{
SriovOp *op;
/* We usually never want to cancel an async write operation, unless it's superseded
* by a newer operation (that resets the state). That is, because we need to ensure
* that we never end up doing two concurrent writes (since we write on a background
* thread, that would be unordered/racy).
* Of course, since we queue requests only per-device, when devices get renamed we
* might end up writing the same sysctl concurrently still. But that's really
* unlikely, and don't rename after udev completes!
*
* The "next" operation is not yet even started. It can be replaced/canceled right away
* when a newer request comes.
* The "pending" operation is currently ongoing, and we may cancel it if
* we have a follow-up operation (queued in "next"). Unless we have a such
* a newer request, we cannot cancel it!
*
* FIXME(shutdown): However, during shutdown we don't have a follow-up write request to cancel
* this operation and we have to give it at least some time to complete. The solution is that
* we register a way to abort the last call during shutdown, and after NM_SHUTDOWN_TIMEOUT_MAX_MSEC
* grace period we pull the plug and cancel it. */
op = g_slice_new(SriovOp);
*op = (SriovOp){
.num_vfs = num_vfs,
.autoprobe = autoprobe,
.callback = callback,
.callback_data = callback_data,
};
sriov_op_queue_op(self, op);
}
static void
device_init_static_sriov_num_vfs(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
int num_vfs;
num_vfs = nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
self,
10,
0,
G_MAXINT32,
-1,
-1);
if (num_vfs >= 0)
sriov_op_queue(self, num_vfs, NM_OPTION_BOOL_DEFAULT, NULL, NULL);
}
}
static void
config_changed(NMConfig *config,
NMConfigData *config_data,
NMConfigChangeFlags changes,
NMConfigData *old_data,
NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->state <= NM_DEVICE_STATE_DISCONNECTED || priv->state >= NM_DEVICE_STATE_ACTIVATED) {
priv->ignore_carrier = nm_config_data_get_ignore_carrier(config_data, self);
if (NM_FLAGS_HAS(changes, NM_CONFIG_CHANGE_VALUES)
&& !nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))
device_init_static_sriov_num_vfs(self);
}
}
static void
realize_start_notify(NMDevice *self, const NMPlatformLink *pllink)
{
/* the default implementation of realize_start_notify() just calls
* link_changed() -- which by default does nothing. */
NM_DEVICE_GET_CLASS(self)->link_changed(self, pllink);
}
/**
* realize_start_setup():
* @self: the #NMDevice
* @plink: the #NMPlatformLink if backed by a kernel netdevice
* @assume_state_guess_assume: set the guess_assume state.
* @assume_state_connection_uuid: set the connection uuid to assume.
* @set_nm_owned: if TRUE and device is a software-device, set nm-owned.
* TRUE.
* @unmanaged_user_explicit: the user-explict unmanaged flag to set.
* @force_platform_init: if TRUE the platform-init unmanaged flag is
* forcefully cleared.
*
* Update the device from backing resource properties (like hardware
* addresses, carrier states, driver/firmware info, etc). This function
* should only change properties for this device, and should not perform
* any tasks that affect other interfaces (like master/slave or parent/child
* stuff).
*/
static void
realize_start_setup(NMDevice *self,
const NMPlatformLink *plink,
gboolean assume_state_guess_assume,
const char *assume_state_connection_uuid,
gboolean set_nm_owned,
NMUnmanFlagOp unmanaged_user_explicit,
gboolean force_platform_init)
{
NMDevicePrivate *priv;
NMDeviceClass *klass;
NMPlatform *platform;
NMDeviceCapabilities capabilities = 0;
NMConfig *config;
guint refresh_rate_ms;
gboolean unmanaged;
/* plink is a NMPlatformLink type, however, we require it to come from the platform
* cache (where else would it come from?). */
nm_assert(!plink || NMP_OBJECT_GET_TYPE(NMP_OBJECT_UP_CAST(plink)) == NMP_OBJECT_TYPE_LINK);
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
/* The device should not be realized */
g_return_if_fail(!priv->real);
g_return_if_fail(nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT));
g_return_if_fail(priv->ip_ifindex <= 0);
g_return_if_fail(priv->ip_iface == NULL);
_LOGD(LOGD_DEVICE,
"start setup of %s, kernel ifindex %d",
G_OBJECT_TYPE_NAME(self),
plink ? plink->ifindex : 0);
klass = NM_DEVICE_GET_CLASS(self);
platform = nm_device_get_platform(self);
/* Balanced by a thaw in nm_device_realize_finish() */
g_object_freeze_notify(G_OBJECT(self));
priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
priv->mtu_initial = 0;
priv->ip6_mtu_initial = 0;
priv->ip6_mtu = 0;
_set_mtu(self, 0);
_assume_state_set(self, assume_state_guess_assume, assume_state_connection_uuid);
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
if (plink)
nm_device_update_from_platform_link(self, plink);
if (priv->ifindex > 0) {
priv->physical_port_id = nm_platform_link_get_physical_port_id(platform, priv->ifindex);
_notify(self, PROP_PHYSICAL_PORT_ID);
priv->dev_id = nm_platform_link_get_dev_id(platform, priv->ifindex);
if (nm_platform_link_is_software(platform, priv->ifindex))
capabilities |= NM_DEVICE_CAP_IS_SOFTWARE;
_set_mtu(self, nm_platform_link_get_mtu(platform, priv->ifindex));
nm_platform_link_get_driver_info(platform,
priv->ifindex,
NULL,
&priv->driver_version,
&priv->firmware_version);
if (priv->driver_version)
_notify(self, PROP_DRIVER_VERSION);
if (priv->firmware_version)
_notify(self, PROP_FIRMWARE_VERSION);
if (nm_platform_link_supports_sriov(platform, priv->ifindex))
capabilities |= NM_DEVICE_CAP_SRIOV;
}
if (klass->get_generic_capabilities)
capabilities |= klass->get_generic_capabilities(self);
_add_capabilities(self, capabilities);
if (!priv->nm_owned && set_nm_owned && nm_device_is_software(self)) {
priv->nm_owned = TRUE;
_LOGD(LOGD_DEVICE, "set nm-owned from state file");
}
if (!priv->udi) {
/* Use a placeholder UDI until we get a real one */
if (priv->udi_id == 0) {
static guint64 udi_id_counter = 0;
priv->udi_id = ++udi_id_counter;
}
priv->udi = g_strdup_printf("/virtual/device/placeholder/%" G_GUINT64_FORMAT, priv->udi_id);
_notify(self, PROP_UDI);
}
nm_device_update_hw_address(self);
nm_device_update_initial_hw_address(self);
nm_device_update_permanent_hw_address(self, FALSE);
/* Note: initial hardware address must be read before calling get_ignore_carrier() */
config = nm_config_get();
priv->ignore_carrier = nm_config_data_get_ignore_carrier(nm_config_get_data(config), self);
if (!priv->config_changed_id) {
priv->config_changed_id = g_signal_connect(config,
NM_CONFIG_SIGNAL_CONFIG_CHANGED,
G_CALLBACK(config_changed),
self);
}
nm_device_set_carrier_from_platform(self);
nm_assert(!priv->stats.timeout_source);
refresh_rate_ms = _stats_refresh_rate_real(priv->stats.refresh_rate_ms);
if (refresh_rate_ms > 0) {
priv->stats.timeout_source =
nm_g_timeout_add_source(refresh_rate_ms, _stats_timeout_cb, self);
}
klass->realize_start_notify(self, plink);
nm_assert(!nm_device_get_unmanaged_mask(self, NM_UNMANAGED_USER_EXPLICIT));
nm_device_set_unmanaged_flags(self, NM_UNMANAGED_USER_EXPLICIT, unmanaged_user_explicit);
/* Do not manage externally created software devices until they are IFF_UP
* or have IP addressing */
nm_device_set_unmanaged_flags(self,
NM_UNMANAGED_EXTERNAL_DOWN,
_dev_unmanaged_is_external_down(self, TRUE));
/* Unmanaged the loopback device with an explicit NM_UNMANAGED_BY_TYPE flag.
* Later we might want to manage 'lo' too. Currently, that doesn't work because
* NetworkManager might down the interface or remove the 127.0.0.1 address. */
nm_device_set_unmanaged_flags(self, NM_UNMANAGED_BY_TYPE, is_loopback(self));
nm_device_set_unmanaged_by_user_udev(self);
nm_device_set_unmanaged_by_user_conf(self);
unmanaged = plink && !plink->initialized && !force_platform_init;
nm_device_set_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT, unmanaged);
}
/**
* nm_device_realize_finish():
* @self: the #NMDevice
* @plink: the #NMPlatformLink if backed by a kernel netdevice
*
* Update the device's master/slave or parent/child relationships from
* backing resource properties. After this function finishes, the device
* is ready for network connectivity.
*/
void
nm_device_realize_finish(NMDevice *self, const NMPlatformLink *plink)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(!plink || link_type_compatible(self, plink->type, NULL, NULL));
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_if_fail(!priv->real);
if (plink)
device_recheck_slave_status(self, plink);
priv->real = TRUE;
_notify(self, PROP_REAL);
nm_device_recheck_available_connections(self);
/* Balanced by a freeze in realize_start_setup(). */
g_object_thaw_notify(G_OBJECT(self));
}
static void
unrealize_notify(NMDevice *self)
{
/* Stub implementation for unrealize_notify(). It does nothing,
* but allows derived classes to uniformly invoke the parent
* implementation. */
}
static gboolean
available_connections_check_delete_unrealized_on_idle(gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
priv = NM_DEVICE_GET_PRIVATE(self);
priv->check_delete_unrealized_id = 0;
if (g_hash_table_size(priv->available_connections) == 0 && !nm_device_is_real(self))
g_signal_emit(self, signals[REMOVED], 0);
return G_SOURCE_REMOVE;
}
static void
available_connections_check_delete_unrealized(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
/* always rescheadule the remove signal. */
nm_clear_g_source(&priv->check_delete_unrealized_id);
if (g_hash_table_size(priv->available_connections) == 0 && !nm_device_is_real(self))
priv->check_delete_unrealized_id =
g_idle_add(available_connections_check_delete_unrealized_on_idle, self);
}
/**
* nm_device_unrealize():
* @self: the #NMDevice
* @remove_resources: if %TRUE, remove backing resources
* @error: location to store error, or %NULL
*
* Clears any properties that depend on backing resources (kernel devices,
* etc) and removes those resources if @remove_resources is %TRUE.
*
* Returns: %TRUE on success, %FALSE on error
*/
gboolean
nm_device_unrealize(NMDevice *self, gboolean remove_resources, GError **error)
{
NMDevicePrivate *priv;
int ifindex;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
if (!nm_device_is_software(self) || !nm_device_is_real(self)) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_SOFTWARE,
"This device is not a software device or is not realized");
return FALSE;
}
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_val_if_fail(priv->iface != NULL, FALSE);
g_return_val_if_fail(priv->real, FALSE);
ifindex = nm_device_get_ifindex(self);
_LOGD(LOGD_DEVICE, "unrealize (ifindex %d)", ifindex > 0 ? ifindex : 0);
nm_device_assume_state_reset(self);
if (remove_resources) {
if (NM_DEVICE_GET_CLASS(self)->unrealize) {
if (!NM_DEVICE_GET_CLASS(self)->unrealize(self, error))
return FALSE;
} else if (ifindex > 0) {
nm_platform_link_delete(nm_device_get_platform(self), ifindex);
}
}
g_object_freeze_notify(G_OBJECT(self));
NM_DEVICE_GET_CLASS(self)->unrealize_notify(self);
_parent_set_ifindex(self, 0, FALSE);
_set_ifindex(self, 0, FALSE);
_set_ifindex(self, 0, TRUE);
if (nm_clear_g_free(&priv->ip_iface_))
update_prop_ip_iface(self);
priv->master_ifindex = 0;
_set_mtu(self, 0);
if (priv->driver_version) {
nm_clear_g_free(&priv->driver_version);
_notify(self, PROP_DRIVER_VERSION);
}
if (priv->firmware_version) {
nm_clear_g_free(&priv->firmware_version);
_notify(self, PROP_FIRMWARE_VERSION);
}
if (priv->udi) {
nm_clear_g_free(&priv->udi);
_notify(self, PROP_UDI);
}
if (priv->path) {
nm_clear_g_free(&priv->path);
_notify(self, PROP_PATH);
}
if (priv->physical_port_id) {
nm_clear_g_free(&priv->physical_port_id);
_notify(self, PROP_PHYSICAL_PORT_ID);
}
nm_clear_g_source_inst(&priv->stats.timeout_source);
_stats_update_counters(self, 0, 0);
priv->hw_addr_len_ = 0;
if (nm_clear_g_free(&priv->hw_addr))
_notify(self, PROP_HW_ADDRESS);
priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
if (nm_clear_g_free(&priv->hw_addr_perm))
_notify(self, PROP_PERM_HW_ADDRESS);
nm_clear_g_free(&priv->hw_addr_initial);
priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities)
priv->capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self);
_notify(self, PROP_CAPABILITIES);
nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id);
nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id);
priv->real = FALSE;
_notify(self, PROP_REAL);
g_object_thaw_notify(G_OBJECT(self));
nm_device_set_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT, TRUE);
nm_device_set_unmanaged_flags(self,
NM_UNMANAGED_PARENT | NM_UNMANAGED_BY_TYPE
| NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_USER_EXPLICIT
| NM_UNMANAGED_EXTERNAL_DOWN | NM_UNMANAGED_IS_SLAVE,
NM_UNMAN_FLAG_OP_FORGET);
nm_device_state_changed(self,
NM_DEVICE_STATE_UNMANAGED,
remove_resources ? NM_DEVICE_STATE_REASON_USER_REQUESTED
: NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
/* Garbage-collect unneeded unrealized devices. */
nm_device_recheck_available_connections(self);
return TRUE;
}
void
nm_device_notify_availability_maybe_changed(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->state != NM_DEVICE_STATE_DISCONNECTED)
return;
/* A device could have stayed disconnected because it would
* want to register with a network server that now become
* available. */
nm_device_recheck_available_connections(self);
if (g_hash_table_size(priv->available_connections) > 0)
nm_device_emit_recheck_auto_activate(self);
}
/**
* nm_device_owns_iface():
* @self: the #NMDevice
* @iface: an interface name
*
* Called by the manager to ask if the device or any of its components owns
* @iface. For example, a WWAN implementation would return %TRUE for an
* ethernet interface name that was owned by the WWAN device's modem component,
* because that ethernet interface is controlled by the WWAN device and cannot
* be used independently of the WWAN device.
*
* Returns: %TRUE if @self or its components own the interface name,
* %FALSE if not
*/
gboolean
nm_device_owns_iface(NMDevice *self, const char *iface)
{
if (NM_DEVICE_GET_CLASS(self)->owns_iface)
return NM_DEVICE_GET_CLASS(self)->owns_iface(self, iface);
return FALSE;
}
NMConnection *
nm_device_new_default_connection(NMDevice *self)
{
NMConnection *connection;
GError *error = NULL;
if (!NM_DEVICE_GET_CLASS(self)->new_default_connection)
return NULL;
connection = NM_DEVICE_GET_CLASS(self)->new_default_connection(self);
if (!connection)
return NULL;
if (!nm_connection_normalize(connection, NULL, NULL, &error)) {
_LOGD(LOGD_DEVICE, "device generated an invalid default connection: %s", error->message);
g_error_free(error);
g_return_val_if_reached(NULL);
}
return connection;
}
static void
slave_state_changed(NMDevice *slave,
NMDeviceState slave_new_state,
NMDeviceState slave_old_state,
NMDeviceStateReason reason,
NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean release = FALSE;
gboolean configure;
_LOGD(LOGD_DEVICE,
"slave %s state change %d (%s) -> %d (%s)",
nm_device_get_iface(slave),
slave_old_state,
nm_device_state_to_string(slave_old_state),
slave_new_state,
nm_device_state_to_string(slave_new_state));
/* Don't try to enslave slaves until the master is ready */
if (priv->state < NM_DEVICE_STATE_CONFIG)
return;
if (slave_new_state == NM_DEVICE_STATE_IP_CONFIG)
nm_device_master_enslave_slave(self, slave, nm_device_get_applied_connection(slave));
else if (slave_new_state > NM_DEVICE_STATE_ACTIVATED)
release = TRUE;
else if (slave_new_state <= NM_DEVICE_STATE_DISCONNECTED
&& slave_old_state > NM_DEVICE_STATE_DISCONNECTED) {
/* Catch failures due to unavailable or unmanaged */
release = TRUE;
}
if (release) {
configure = priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED
&& nm_device_sys_iface_state_get(slave) != NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
nm_device_master_release_one_slave(self, slave, configure, FALSE, reason);
/* Bridge/bond/team interfaces are left up until manually deactivated */
if (c_list_is_empty(&priv->slaves) && priv->state == NM_DEVICE_STATE_ACTIVATED)
_LOGD(LOGD_DEVICE, "last slave removed; remaining activated");
}
}
/**
* nm_device_master_add_slave:
* @self: the master device
* @slave: the slave device to enslave
* @configure: pass %TRUE if the slave should be configured by the master, or
* %FALSE if it is already configured outside NetworkManager
*
* If @self is capable of enslaving other devices (ie it's a bridge, bond, team,
* etc) then this function adds @slave to the slave list for later enslavement.
*
* Returns: %TRUE if the slave was enslaved. %FALSE means, the slave was already
* enslaved and nothing was done.
*/
static gboolean
nm_device_master_add_slave(NMDevice *self, NMDevice *slave, gboolean configure)
{
NMDevicePrivate *priv;
NMDevicePrivate *slave_priv;
SlaveInfo *info;
gboolean changed = FALSE;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_DEVICE(slave), FALSE);
g_return_val_if_fail(NM_DEVICE_GET_CLASS(self)->enslave_slave != NULL, FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
slave_priv = NM_DEVICE_GET_PRIVATE(slave);
info = find_slave_info(self, slave);
_LOGT(LOGD_CORE,
"master: add one slave %p/%s%s",
slave,
nm_device_get_iface(slave),
info ? " (already registered)" : "");
if (configure)
g_return_val_if_fail(nm_device_get_state(slave) >= NM_DEVICE_STATE_DISCONNECTED, FALSE);
if (!info) {
g_return_val_if_fail(!slave_priv->master, FALSE);
g_return_val_if_fail(!slave_priv->is_enslaved, FALSE);
info = g_slice_new0(SlaveInfo);
info->slave = g_object_ref(slave);
info->configure = configure;
info->watch_id =
g_signal_connect(slave, NM_DEVICE_STATE_CHANGED, G_CALLBACK(slave_state_changed), self);
c_list_link_tail(&priv->slaves, &info->lst_slave);
slave_priv->master = g_object_ref(self);
_active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_MASTER_HAS_SLAVES);
/* no need to emit
*
* _notify (slave, PROP_MASTER);
*
* because slave_priv->is_enslaved is not true, thus the value
* didn't change yet. */
g_warn_if_fail(!NM_FLAGS_HAS(slave_priv->unmanaged_mask, NM_UNMANAGED_IS_SLAVE));
nm_device_set_unmanaged_by_flags(slave,
NM_UNMANAGED_IS_SLAVE,
FALSE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
changed = TRUE;
} else
g_return_val_if_fail(slave_priv->master == self, FALSE);
nm_device_queue_recheck_assume(self);
nm_device_queue_recheck_assume(slave);
return changed;
}
/**
* nm_device_master_check_slave_physical_port:
* @self: the master device
* @slave: a slave device
* @log_domain: domain to log a warning in
*
* Checks if @self already has a slave with the same #NMDevice:physical-port-id
* as @slave, and logs a warning if so.
*/
void
nm_device_master_check_slave_physical_port(NMDevice *self, NMDevice *slave, NMLogDomain log_domain)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *slave_physical_port_id, *existing_physical_port_id;
SlaveInfo *info;
CList *iter;
slave_physical_port_id = nm_device_get_physical_port_id(slave);
if (!slave_physical_port_id)
return;
c_list_for_each (iter, &priv->slaves) {
info = c_list_entry(iter, SlaveInfo, lst_slave);
if (info->slave == slave)
continue;
existing_physical_port_id = nm_device_get_physical_port_id(info->slave);
if (nm_streq0(slave_physical_port_id, existing_physical_port_id)) {
_LOGW(log_domain,
"slave %s shares a physical port with existing slave %s",
nm_device_get_ip_iface(slave),
nm_device_get_ip_iface(info->slave));
/* Since this function will get called for every slave, we only have
* to warn about the first match we find; if there are other matches
* later in the list, we will have already warned about them matching
* @existing earlier.
*/
return;
}
}
}
/* release all slaves */
void
nm_device_master_release_slaves(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceStateReason reason;
CList *iter, *safe;
/* Don't release the slaves if this connection doesn't belong to NM. */
if (nm_device_sys_iface_state_is_external(self))
return;
reason = priv->state_reason;
if (priv->state == NM_DEVICE_STATE_FAILED)
reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
c_list_for_each_safe (iter, safe, &priv->slaves) {
SlaveInfo *info = c_list_entry(iter, SlaveInfo, lst_slave);
if (priv->activation_state_preserve_external_ports
&& nm_device_sys_iface_state_is_external(info->slave)) {
_LOGT(LOGD_DEVICE,
"master: preserve external port %s",
nm_device_get_iface(info->slave));
continue;
}
nm_device_master_release_one_slave(self, info->slave, TRUE, FALSE, reason);
}
/* We only need this flag for a short time. It served its purpose. Clear
* it again. */
nm_device_activation_state_set_preserve_external_ports(self, FALSE);
}
/**
* nm_device_is_master:
* @self: the device
*
* Returns: %TRUE if the device can have slaves
*/
gboolean
nm_device_is_master(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return NM_DEVICE_GET_CLASS(self)->is_master;
}
/**
* nm_device_get_master:
* @self: the device
*
* If @self has been enslaved by another device, this returns that
* device. Otherwise, it returns %NULL. (In particular, note that if
* @self is in the process of activating as a slave, but has not yet
* been enslaved by its master, this will return %NULL.)
*
* Returns: (transfer none): @self's master, or %NULL
*/
NMDevice *
nm_device_get_master(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->is_enslaved) {
g_return_val_if_fail(priv->master, NULL);
return priv->master;
}
return NULL;
}
/**
* nm_device_slave_notify_enslave:
* @self: the slave device
* @success: whether the enslaving operation succeeded
*
* Notifies a slave that either it has been enslaved, or else its master tried
* to enslave it and failed.
*/
static void
nm_device_slave_notify_enslave(NMDevice *self, gboolean success)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection = nm_device_get_applied_connection(self);
gboolean activating = (priv->state == NM_DEVICE_STATE_IP_CONFIG);
g_return_if_fail(priv->master);
if (!priv->is_enslaved) {
if (success) {
if (activating) {
_LOGI(LOGD_DEVICE,
"Activation: connection '%s' enslaved, continuing activation",
nm_connection_get_id(connection));
} else
_LOGI(LOGD_DEVICE, "enslaved to %s", nm_device_get_iface(priv->master));
priv->is_enslaved = TRUE;
_notify(self, PROP_MASTER);
nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
} else if (activating) {
_LOGW(LOGD_DEVICE,
"Activation: connection '%s' could not be enslaved",
nm_connection_get_id(connection));
}
}
if (!activating) {
nm_device_queue_recheck_assume(self);
return;
}
if (!success) {
nm_device_queue_state(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
return;
}
nm_device_activate_schedule_stage3_ip_config(self, FALSE);
}
/**
* nm_device_slave_notify_release:
* @self: the slave device
* @reason: the reason associated with the state change
*
* Notifies a slave that it has been released, and why.
*/
static void
nm_device_slave_notify_release(NMDevice *self, NMDeviceStateReason reason)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection = nm_device_get_applied_connection(self);
const char *master_status;
g_return_if_fail(priv->master);
if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state <= NM_DEVICE_STATE_ACTIVATED) {
switch (nm_device_state_reason_check(reason)) {
case NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED:
master_status = "failed";
break;
case NM_DEVICE_STATE_REASON_USER_REQUESTED:
reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
master_status = "deactivated by user request";
break;
case NM_DEVICE_STATE_REASON_CONNECTION_REMOVED:
reason = NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED;
master_status = "deactivated because master was removed";
break;
default:
master_status = "deactivated";
break;
}
_LOGD(LOGD_DEVICE,
"Activation: connection '%s' master %s",
nm_connection_get_id(connection),
master_status);
/* Cancel any pending activation sources */
_cancel_activation(self);
nm_device_queue_state(self, NM_DEVICE_STATE_DEACTIVATING, reason);
} else
_LOGI(LOGD_DEVICE, "released from master device %s", nm_device_get_iface(priv->master));
if (priv->is_enslaved) {
priv->is_enslaved = FALSE;
_notify(self, PROP_MASTER);
nm_clear_pointer(&NM_DEVICE_GET_PRIVATE(priv->master)->ports_variant, g_variant_unref);
nm_gobject_notify_together(priv->master, PROP_PORTS, PROP_SLAVES);
}
}
/**
* nm_device_removed:
* @self: the #NMDevice
* @unconfigure_ip_config: whether to clear the IP config objects
* of the device (provided, it is still not cleared at this point).
*
* Called by the manager when the device was removed. Releases the device from
* the master in case it's enslaved.
*/
void
nm_device_removed(NMDevice *self, gboolean unconfigure_ip_config)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
_dev_ipdhcpx_cleanup(self, AF_INET, TRUE, FALSE);
_dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->master) {
/* this is called when something externally messes with the slave or during shut-down.
* Release the slave from master, but don't touch the device. */
nm_device_master_release_one_slave(priv->master,
self,
FALSE,
FALSE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
_dev_l3_register_l3cds(self, priv->l3cfg, FALSE, unconfigure_ip_config);
}
static gboolean
is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->carrier || priv->ignore_carrier)
return TRUE;
if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER))
return TRUE;
/* master types are always available even without carrier. */
if (nm_device_is_master(self))
return TRUE;
return FALSE;
}
/**
* nm_device_is_available:
* @self: the #NMDevice
* @flags: additional flags to influence the check. Flags have the
* meaning to increase the availability of a device.
*
* Checks if @self would currently be capable of activating a
* connection. In particular, it checks that the device is ready (eg,
* is not missing firmware), that it has carrier (if necessary), and
* that any necessary external software (eg, ModemManager,
* wpa_supplicant) is available.
*
* @self can only be in a state higher than
* %NM_DEVICE_STATE_UNAVAILABLE when nm_device_is_available() returns
* %TRUE. (But note that it can still be %NM_DEVICE_STATE_UNMANAGED
* when it is available.)
*
* Returns: %TRUE or %FALSE
*/
gboolean
nm_device_is_available(NMDevice *self, NMDeviceCheckDevAvailableFlags flags)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->firmware_missing)
return FALSE;
return NM_DEVICE_GET_CLASS(self)->is_available(self, flags);
}
gboolean
nm_device_ignore_carrier_by_default(NMDevice *self)
{
/* master types ignore-carrier by default. */
return nm_device_is_master(self);
}
gboolean
nm_device_get_enabled(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
if (NM_DEVICE_GET_CLASS(self)->get_enabled)
return NM_DEVICE_GET_CLASS(self)->get_enabled(self);
return TRUE;
}
void
nm_device_set_enabled(NMDevice *self, gboolean enabled)
{
g_return_if_fail(NM_IS_DEVICE(self));
if (NM_DEVICE_GET_CLASS(self)->set_enabled)
NM_DEVICE_GET_CLASS(self)->set_enabled(self, enabled);
}
static NM_UTILS_FLAGS2STR_DEFINE(_autoconnect_blocked_flags_to_string,
NMDeviceAutoconnectBlockedFlags,
NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_NONE, "none"),
NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_USER, "user"),
NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_WRONG_PIN,
"wrong-pin"),
NM_UTILS_FLAGS2STR(NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT,
"manual-disconnect"), );
NMDeviceAutoconnectBlockedFlags
nm_device_autoconnect_blocked_get(NMDevice *self, NMDeviceAutoconnectBlockedFlags mask)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
if (mask == 0)
mask = NM_DEVICE_AUTOCONNECT_BLOCKED_ALL;
priv = NM_DEVICE_GET_PRIVATE(self);
return priv->autoconnect_blocked_flags & mask;
}
void
nm_device_autoconnect_blocked_set_full(NMDevice *self,
NMDeviceAutoconnectBlockedFlags mask,
NMDeviceAutoconnectBlockedFlags value)
{
NMDevicePrivate *priv;
gboolean changed;
char buf1[128], buf2[128];
g_return_if_fail(NM_IS_DEVICE(self));
nm_assert(mask);
nm_assert(!NM_FLAGS_ANY(mask, ~NM_DEVICE_AUTOCONNECT_BLOCKED_ALL));
nm_assert(!NM_FLAGS_ANY(value, ~mask));
priv = NM_DEVICE_GET_PRIVATE(self);
value = (priv->autoconnect_blocked_flags & ~mask) | (mask & value);
if (value == priv->autoconnect_blocked_flags)
return;
changed = ((!value) != (!priv->autoconnect_blocked_flags));
_LOGT(
LOGD_DEVICE,
"autoconnect-blocked: set \"%s\" (was \"%s\")",
_autoconnect_blocked_flags_to_string(value, buf1, sizeof(buf1)),
_autoconnect_blocked_flags_to_string(priv->autoconnect_blocked_flags, buf2, sizeof(buf2)));
priv->autoconnect_blocked_flags = value;
nm_assert(priv->autoconnect_blocked_flags == value);
if (changed)
_notify(self, PROP_AUTOCONNECT);
}
static gboolean
autoconnect_allowed_accumulator(GSignalInvocationHint *ihint,
GValue *return_accu,
const GValue *handler_return,
gpointer data)
{
if (!g_value_get_boolean(handler_return))
g_value_set_boolean(return_accu, FALSE);
return TRUE;
}
/**
* nm_device_autoconnect_allowed:
* @self: the #NMDevice
*
* Returns: %TRUE if the device can be auto-connected immediately, taking
* transient conditions into account (like companion devices that may wish to
* block autoconnect for a time).
*/
gboolean
nm_device_autoconnect_allowed(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
GValue instance = G_VALUE_INIT;
GValue retval = G_VALUE_INIT;
if (nm_device_autoconnect_blocked_get(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL))
return FALSE;
if (klass->get_autoconnect_allowed && !klass->get_autoconnect_allowed(self))
return FALSE;
if (!nm_device_get_enabled(self))
return FALSE;
if (nm_device_is_real(self)) {
if (priv->state < NM_DEVICE_STATE_DISCONNECTED)
return FALSE;
} else {
if (!nm_device_check_unrealized_device_managed(self))
return FALSE;
}
if (priv->delete_on_deactivate_data)
return FALSE;
/* The 'autoconnect-allowed' signal is emitted on a device to allow
* other listeners to block autoconnect on the device if they wish.
* This is mainly used by the OLPC Mesh devices to block autoconnect
* on their companion Wi-Fi device as they share radio resources and
* cannot be connected at the same time.
*/
g_value_init(&instance, G_TYPE_OBJECT);
g_value_set_object(&instance, self);
g_value_init(&retval, G_TYPE_BOOLEAN);
g_value_set_boolean(&retval, TRUE);
/* Use g_signal_emitv() rather than g_signal_emit() to avoid the return
* value being changed if no handlers are connected */
g_signal_emitv(&instance, signals[AUTOCONNECT_ALLOWED], 0, &retval);
g_value_unset(&instance);
return g_value_get_boolean(&retval);
}
static gboolean
can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object)
{
nm_assert(!specific_object || !*specific_object);
return TRUE;
}
/**
* nm_device_can_auto_connect:
* @self: an #NMDevice
* @sett_conn: a #NMSettingsConnection
* @specific_object: (out) (transfer full): on output, the path of an
* object associated with the returned connection, to be passed to
* nm_manager_activate_connection(), or %NULL.
*
* Checks if @sett_conn can be auto-activated on @self right now.
* This requires, at a minimum, that the connection be compatible with
* @self, and that it have the #NMSettingConnection:autoconnect property
* set, and that the device allow auto connections. Some devices impose
* additional requirements. (Eg, a Wi-Fi connection can only be activated
* if its SSID was seen in the last scan.)
*
* Returns: %TRUE, if the @sett_conn can be auto-activated.
**/
gboolean
nm_device_can_auto_connect(NMDevice *self, NMSettingsConnection *sett_conn, char **specific_object)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn), FALSE);
g_return_val_if_fail(!specific_object || !*specific_object, FALSE);
/* the caller must ensure that nm_device_autoconnect_allowed() returns
* TRUE as well. This is done, because nm_device_can_auto_connect()
* has only one caller, and it iterates over a list of available
* connections.
*
* Hence, we don't need to re-check nm_device_autoconnect_allowed()
* over and over again. The caller is supposed to do that. */
nm_assert(nm_device_autoconnect_allowed(self));
if (!nm_device_check_connection_available(self,
nm_settings_connection_get_connection(sett_conn),
NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
NULL,
NULL))
return FALSE;
if (!NM_DEVICE_GET_CLASS(self)->can_auto_connect(self, sett_conn, specific_object))
return FALSE;
return TRUE;
}
static gboolean
device_has_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NMDedupMultiHeadEntry *head_entry;
const NMPlatformLink *pllink;
NMPLookup lookup;
pllink = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
if (!pllink)
return FALSE;
if (pllink->master > 0) {
/* Master-slave relationship is also a configuration */
return TRUE;
}
head_entry = nm_platform_lookup(
nm_device_get_platform(self),
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP4_ADDRESS, pllink->ifindex));
if (head_entry)
return TRUE;
head_entry = nm_platform_lookup(
nm_device_get_platform(self),
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP6_ADDRESS, pllink->ifindex));
if (head_entry)
return TRUE;
if (nm_device_is_software(self) && nm_device_is_real(self)) {
/* The existence of a software device is good enough. */
return TRUE;
}
return FALSE;
}
/**
* nm_device_master_update_slave_connection:
* @self: the master #NMDevice
* @slave: the slave #NMDevice
* @connection: the #NMConnection to update with the slave settings
* @GError: (out): error description
*
* Reads the slave configuration for @slave and updates @connection with those
* properties. This invokes a virtual function on the master device @self.
*
* Returns: %TRUE if the configuration was read and @connection updated,
* %FALSE on failure.
*/
gboolean
nm_device_master_update_slave_connection(NMDevice *self,
NMDevice *slave,
NMConnection *connection,
GError **error)
{
NMDeviceClass *klass;
gboolean success;
g_return_val_if_fail(self, FALSE);
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(slave, FALSE);
g_return_val_if_fail(connection, FALSE);
g_return_val_if_fail(!error || !*error, FALSE);
g_return_val_if_fail(nm_connection_get_setting_connection(connection), FALSE);
g_return_val_if_fail(nm_device_get_iface(self), FALSE);
klass = NM_DEVICE_GET_CLASS(self);
if (klass->master_update_slave_connection) {
success = klass->master_update_slave_connection(self, slave, connection, error);
g_return_val_if_fail(!error || (success && !*error) || *error, success);
return success;
}
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"master device '%s' cannot update a slave connection for slave device '%s' (master "
"type not supported?)",
nm_device_get_iface(self),
nm_device_get_iface(slave));
return FALSE;
}
static gboolean
_get_maybe_ipv6_disabled(NMDevice *self)
{
NMPlatform *platform;
int ifindex;
const char *path;
char ifname[IFNAMSIZ];
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
return FALSE;
platform = nm_device_get_platform(self);
if (!nm_platform_if_indextoname(platform, ifindex, ifname))
return FALSE;
path = nm_sprintf_bufa(128, "/proc/sys/net/ipv6/conf/%s/disable_ipv6", ifname);
return (nm_platform_sysctl_get_int32(platform, NMP_SYSCTL_PATHID_ABSOLUTE(path), 0) == 0);
}
/*
* nm_device_generate_connection:
*
* Generates a connection from an existing interface.
*
* If the device doesn't have an IP configuration and it's not a port or a
* controller, then no connection gets generated and the function returns
* %NULL. In such case, @maybe_later is set to %TRUE if a connection can be
* generated later when an IP address is assigned to the interface.
*/
NMConnection *
nm_device_generate_connection(NMDevice *self,
NMDevice *master,
gboolean *out_maybe_later,
GError **error)
{
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *ifname = nm_device_get_iface(self);
gs_unref_object NMConnection *connection = NULL;
NMSetting *s_con;
NMSetting *s_ip4;
NMSetting *s_ip6;
char uuid[37];
const char *ip4_method, *ip6_method;
GError *local = NULL;
const NMPlatformLink *pllink;
NM_SET_OUT(out_maybe_later, FALSE);
/* If update_connection() is not implemented, just fail. */
if (!klass->update_connection) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"device class %s does not support generating a connection",
G_OBJECT_TYPE_NAME(self));
return NULL;
}
/* Return NULL if device is unconfigured. */
if (!device_has_config(self)) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"device has no existing configuration");
return NULL;
}
connection = nm_simple_connection_new();
s_con = nm_setting_connection_new();
g_object_set(s_con,
NM_SETTING_CONNECTION_UUID,
nm_uuid_generate_random_str_arr(uuid),
NM_SETTING_CONNECTION_ID,
ifname,
NM_SETTING_CONNECTION_AUTOCONNECT,
FALSE,
NM_SETTING_CONNECTION_INTERFACE_NAME,
ifname,
NM_SETTING_CONNECTION_TIMESTAMP,
(guint64) time(NULL),
NULL);
if (klass->connection_type_supported)
g_object_set(s_con, NM_SETTING_CONNECTION_TYPE, klass->connection_type_supported, NULL);
nm_connection_add_setting(connection, s_con);
/* If the device is a slave, update various slave settings */
if (master) {
if (!nm_device_master_update_slave_connection(master, self, connection, &local)) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"master device '%s' failed to update slave connection: %s",
nm_device_get_iface(master),
local->message);
g_error_free(local);
return NULL;
}
} else {
/* Only regular and master devices get IP configuration; slaves do not */
s_ip4 = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
AF_INET,
nm_device_get_ip_ifindex(self),
FALSE);
nm_connection_add_setting(connection, s_ip4);
s_ip6 = nm_utils_platform_capture_ip_setting(nm_device_get_platform(self),
AF_INET6,
nm_device_get_ip_ifindex(self),
_get_maybe_ipv6_disabled(self));
nm_connection_add_setting(connection, s_ip6);
nm_connection_add_setting(connection, nm_setting_proxy_new());
pllink = nm_platform_link_get(nm_device_get_platform(self), priv->ifindex);
if (pllink && pllink->inet6_token.id) {
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
g_object_set(s_ip6,
NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE,
NM_IN6_ADDR_GEN_MODE_EUI64,
NM_SETTING_IP6_CONFIG_TOKEN,
nm_utils_inet6_interface_identifier_to_token(&pllink->inet6_token, sbuf),
NULL);
}
}
klass->update_connection(self, connection);
if (!nm_connection_normalize(connection, NULL, NULL, &local)) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"generated connection does not verify: %s",
local->message);
g_error_free(local);
return NULL;
}
/* Ignore the connection if it has no IP configuration,
* no slave configuration, and is not a master interface.
*/
ip4_method = nm_utils_get_ip_config_method(connection, AF_INET);
ip6_method = nm_utils_get_ip_config_method(connection, AF_INET6);
if (nm_streq0(ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
&& NM_IN_STRSET(ip6_method,
NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED)
&& !nm_setting_connection_get_master(NM_SETTING_CONNECTION(s_con))
&& c_list_is_empty(&priv->slaves)) {
NM_SET_OUT(out_maybe_later, TRUE);
g_set_error_literal(
error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"ignoring generated connection (no IP and not in master-slave relationship)");
return NULL;
}
/* Ignore any IPv6LL-only, not master connections without slaves,
* unless they are in the assume-ipv6ll-only list.
*/
if (nm_streq0(ip4_method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)
&& nm_streq0(ip6_method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)
&& !nm_setting_connection_get_master(NM_SETTING_CONNECTION(s_con))
&& c_list_is_empty(&priv->slaves)
&& !nm_config_data_get_assume_ipv6ll_only(NM_CONFIG_GET_DATA, self)) {
_LOGD(LOGD_DEVICE,
"ignoring generated connection (IPv6LL-only and not in master-slave relationship)");
NM_SET_OUT(out_maybe_later, TRUE);
g_set_error_literal(
error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"ignoring generated connection (IPv6LL-only and not in master-slave relationship)");
return NULL;
}
return g_steal_pointer(&connection);
}
/**
* nm_device_complete_connection:
*
* Complete the connection. This is solely used for AddAndActivate where the user
* may pass in an incomplete connection and a device, and the device tries to
* make sense of it and complete it for activation. Otherwise, this is not
* used.
*
* Returns: success or failure.
*/
gboolean
nm_device_complete_connection(NMDevice *self,
NMConnection *connection,
const char *specific_object,
NMConnection *const *existing_connections,
GError **error)
{
NMDeviceClass *klass;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
klass = NM_DEVICE_GET_CLASS(self);
if (!klass->complete_connection) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INVALID_CONNECTION,
"Device class %s had no complete_connection method",
G_OBJECT_TYPE_NAME(self));
return FALSE;
}
if (!klass->complete_connection(self, connection, specific_object, existing_connections, error))
return FALSE;
if (!nm_connection_normalize(connection, NULL, NULL, error))
return FALSE;
return nm_device_check_connection_compatible(self, connection, error);
}
gboolean
nm_device_match_parent(NMDevice *self, const char *parent)
{
NMDevice *parent_device;
g_return_val_if_fail(parent, FALSE);
parent_device = nm_device_parent_get_device(self);
if (!parent_device)
return FALSE;
if (nm_utils_is_uuid(parent)) {
NMConnection *connection;
/* If the parent is a UUID, the connection matches when there is
* no connection active on the device or when a connection with
* that UUID is active.
*/
connection = nm_device_get_applied_connection(parent_device);
if (connection && !nm_streq0(parent, nm_connection_get_uuid(connection)))
return FALSE;
} else {
/* Interface name */
if (!nm_streq0(parent, nm_device_get_ip_iface(parent_device)))
return FALSE;
}
return TRUE;
}
gboolean
nm_device_match_parent_hwaddr(NMDevice *device,
NMConnection *connection,
gboolean fail_if_no_hwaddr)
{
NMSettingWired *s_wired;
NMDevice *parent_device;
const char *setting_mac;
const char *parent_mac;
s_wired = nm_connection_get_setting_wired(connection);
if (!s_wired)
return !fail_if_no_hwaddr;
setting_mac = nm_setting_wired_get_mac_address(s_wired);
if (!setting_mac)
return !fail_if_no_hwaddr;
parent_device = nm_device_parent_get_device(device);
if (!parent_device)
return !fail_if_no_hwaddr;
parent_mac = nm_device_get_permanent_hw_address(parent_device);
return parent_mac && nm_utils_hwaddr_matches(setting_mac, -1, parent_mac, -1);
}
static gboolean
check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *device_iface = nm_device_get_iface(self);
gs_free_error GError *local = NULL;
gs_free char *conn_iface = NULL;
NMDeviceClass *klass;
NMSettingMatch *s_match;
const GSList *specs;
gboolean has_match = FALSE;
klass = NM_DEVICE_GET_CLASS(self);
if (klass->connection_type_check_compatible) {
if (!_nm_connection_check_main_setting(connection,
klass->connection_type_check_compatible,
error))
return FALSE;
} else if (klass->check_connection_compatible == check_connection_compatible) {
/* the device class does not implement check_connection_compatible nor set
* connection_type_check_compatible. That means, it is by default not compatible
* with any connection type. */
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
"device does not support any connections");
return FALSE;
}
conn_iface = nm_manager_get_connection_iface(NM_MANAGER_GET, connection, NULL, NULL, &local);
/* We always need a interface name for virtual devices, but for
* physical ones a connection without interface name is fine for
* any device. */
if (!conn_iface) {
if (nm_connection_is_virtual(connection)) {
nm_utils_error_set(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"cannot get interface name due to %s",
local->message);
return FALSE;
}
} else if (!nm_streq0(conn_iface, device_iface)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"mismatching interface name");
return FALSE;
}
s_match = (NMSettingMatch *) nm_connection_get_setting(connection, NM_TYPE_SETTING_MATCH);
if (s_match) {
const char *const *patterns;
guint num_patterns = 0;
patterns = nm_setting_match_get_interface_names(s_match, &num_patterns);
if (num_patterns > 0 && !nm_wildcard_match_check(device_iface, patterns, num_patterns)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device does not satisfy match.interface-name property");
return FALSE;
}
patterns = nm_setting_match_get_kernel_command_lines(s_match, &num_patterns);
if (num_patterns > 0
&& !nm_utils_kernel_cmdline_match_check(nm_utils_proc_cmdline_split(),
patterns,
num_patterns,
error))
return FALSE;
patterns = nm_setting_match_get_drivers(s_match, &num_patterns);
if (num_patterns > 0
&& !nm_wildcard_match_check(nm_device_get_driver(self), patterns, num_patterns)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device does not satisfy match.driver property");
return FALSE;
}
patterns = nm_setting_match_get_paths(s_match, &num_patterns);
if (num_patterns > 0 && !nm_wildcard_match_check(priv->path, patterns, num_patterns)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
"device does not satisfy match.path property");
return FALSE;
}
}
specs =
nm_config_data_get_device_allowed_connections_specs(NM_CONFIG_GET_DATA, self, &has_match);
if (has_match && !nm_utils_connection_match_spec_list(connection, specs, FALSE)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_DISALLOWED,
"device configuration doesn't allow this connection");
return FALSE;
}
return TRUE;
}
/**
* nm_device_check_connection_compatible:
* @self: an #NMDevice
* @connection: an #NMConnection
* @error: optional reason why it is incompatible. Note that the
* error code is set to %NM_UTILS_ERROR_CONNECTION_AVAILABLE_INCOMPATIBLE,
* if the profile is fundamentally incompatible with the device
* (most commonly, because the device-type does not support the
* connection-type).
*
* Checks if @connection could potentially be activated on @self.
* This means only that @self has the proper capabilities, and that
* @connection is not locked to some other device. It does not
* necessarily mean that @connection could be activated on @self
* right now. (Eg, it might refer to a Wi-Fi network that is not
* currently available.)
*
* Returns: #TRUE if @connection could potentially be activated on
* @self.
*/
gboolean
nm_device_check_connection_compatible(NMDevice *self, NMConnection *connection, GError **error)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
return NM_DEVICE_GET_CLASS(self)->check_connection_compatible(self, connection, error);
}
gboolean
nm_device_check_slave_connection_compatible(NMDevice *self, NMConnection *slave)
{
NMSettingConnection *s_con;
const char *connection_type, *slave_type;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_CONNECTION(slave), FALSE);
if (!nm_device_is_master(self))
return FALSE;
/* All masters should have connection type set */
connection_type = NM_DEVICE_GET_CLASS(self)->connection_type_supported;
g_return_val_if_fail(connection_type, FALSE);
s_con = nm_connection_get_setting_connection(slave);
g_assert(s_con);
slave_type = nm_setting_connection_get_slave_type(s_con);
if (!slave_type)
return FALSE;
return nm_streq(connection_type, slave_type);
}
/**
* nm_device_can_assume_connections:
* @self: #NMDevice instance
*
* This is a convenience function to determine whether connection assumption
* is available for this device.
*
* Returns: %TRUE if the device is capable of assuming connections, %FALSE if not
*/
gboolean
nm_device_can_assume_connections(NMDevice *self)
{
return !!NM_DEVICE_GET_CLASS(self)->update_connection;
}
static gboolean
unmanaged_on_quit(NMDevice *self)
{
NMConnection *connection;
/* NMDeviceWifi overwrites this function to always unmanage wifi devices.
*
* For all other types, if the device type can assume connections, we leave
* it up on quit.
*
* Originally, we would only keep devices up that can be assumed afterwards.
* However, that meant we unmanged layer-2 only devices. So, this was step
* by step refined to unmanage less (commit 25aaaab3, rh#1311988, rh#1333983).
* But there are more scenarios where we also want to keep the device up
* (rh#1378418, rh#1371126). */
if (!nm_device_can_assume_connections(self))
return TRUE;
/* the only exception are IPv4 shared connections. We unmanage them on quit. */
connection = nm_device_get_applied_connection(self);
if (connection) {
if (NM_IN_STRSET(nm_utils_get_ip_config_method(connection, AF_INET),
NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
/* shared connections are to be unmangaed. */
return TRUE;
}
}
return FALSE;
}
gboolean
nm_device_unmanage_on_quit(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return NM_DEVICE_GET_CLASS(self)->unmanaged_on_quit(self);
}
static gboolean
nm_device_emit_recheck_assume(gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
priv = NM_DEVICE_GET_PRIVATE(self);
priv->recheck_assume_id = 0;
if (!nm_device_get_act_request(self))
g_signal_emit(self, signals[RECHECK_ASSUME], 0);
return G_SOURCE_REMOVE;
}
void
nm_device_queue_recheck_assume(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->recheck_assume_id && nm_device_can_assume_connections(self))
priv->recheck_assume_id = g_idle_add(nm_device_emit_recheck_assume, self);
}
static gboolean
recheck_available(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean now_available;
NMDeviceState state = nm_device_get_state(self);
NMDeviceState new_state = NM_DEVICE_STATE_UNKNOWN;
priv->recheck_available.call_id = 0;
now_available = nm_device_is_available(self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE);
if (state == NM_DEVICE_STATE_UNAVAILABLE && now_available) {
new_state = NM_DEVICE_STATE_DISCONNECTED;
nm_device_queue_state(self, new_state, priv->recheck_available.available_reason);
} else if (state >= NM_DEVICE_STATE_DISCONNECTED && !now_available) {
new_state = NM_DEVICE_STATE_UNAVAILABLE;
nm_device_queue_state(self, new_state, priv->recheck_available.unavailable_reason);
}
if (new_state > NM_DEVICE_STATE_UNKNOWN) {
_LOGD(LOGD_DEVICE,
"is %savailable, %s %s",
now_available ? "" : "not ",
new_state == NM_DEVICE_STATE_UNAVAILABLE ? "no change required for"
: "will transition to",
nm_device_state_to_string(new_state == NM_DEVICE_STATE_UNAVAILABLE ? state
: new_state));
priv->recheck_available.available_reason = NM_DEVICE_STATE_REASON_NONE;
priv->recheck_available.unavailable_reason = NM_DEVICE_STATE_REASON_NONE;
}
if (priv->recheck_available.call_id == 0)
nm_device_remove_pending_action(self, NM_PENDING_ACTION_RECHECK_AVAILABLE, TRUE);
return G_SOURCE_REMOVE;
}
void
nm_device_queue_recheck_available(NMDevice *self,
NMDeviceStateReason available_reason,
NMDeviceStateReason unavailable_reason)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->recheck_available.available_reason = available_reason;
priv->recheck_available.unavailable_reason = unavailable_reason;
if (!priv->recheck_available.call_id) {
priv->recheck_available.call_id = g_idle_add(recheck_available, self);
nm_device_add_pending_action (self, NM_PENDING_ACTION_RECHECK_AVAILABLE,
FALSE /* cannot assert, because of how recheck_available() first clears
* the call-id and postpones removing the pending-action. */);
}
}
void
nm_device_emit_recheck_auto_activate(NMDevice *self)
{
g_signal_emit(self, signals[RECHECK_AUTO_ACTIVATE], 0);
}
void
nm_device_auth_request(NMDevice *self,
GDBusMethodInvocation *context,
NMConnection *connection,
const char *permission,
gboolean allow_interaction,
GCancellable *cancellable,
NMManagerDeviceAuthRequestFunc callback,
gpointer user_data)
{
nm_manager_device_auth_request(nm_device_get_manager(self),
self,
context,
connection,
permission,
allow_interaction,
cancellable,
callback,
user_data);
}
/*****************************************************************************/
static void
activation_source_clear(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (nm_clear_g_source_inst(&priv->activation_idle_source)) {
_LOGD(LOGD_DEVICE,
"activation-stage: clear %s",
_activation_func_to_string(priv->activation_func));
priv->activation_func = NULL;
}
}
static gboolean
activation_source_handle_cb(gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv;
ActivationHandleFunc activation_func;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_val_if_fail(priv->activation_idle_source, G_SOURCE_REMOVE);
nm_assert(priv->activation_func);
activation_func = priv->activation_func;
priv->activation_func = NULL;
nm_clear_g_source_inst(&priv->activation_idle_source);
_LOGD(LOGD_DEVICE, "activation-stage: invoke %s", _activation_func_to_string(activation_func));
activation_func(self);
return G_SOURCE_CONTINUE;
}
static void
activation_source_schedule(NMDevice *self, ActivationHandleFunc func)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->activation_idle_source && priv->activation_func == func) {
/* Scheduling the same stage multiple times is fine. */
_LOGT(LOGD_DEVICE,
"activation-stage: already scheduled %s",
_activation_func_to_string(func));
return;
}
if (priv->activation_idle_source) {
_LOGD(LOGD_DEVICE,
"activation-stage: schedule %s (which replaces %s)",
_activation_func_to_string(func),
_activation_func_to_string(priv->activation_func));
nm_clear_g_source_inst(&priv->activation_idle_source);
} else {
_LOGD(LOGD_DEVICE, "activation-stage: schedule %s", _activation_func_to_string(func));
}
priv->activation_idle_source = nm_g_idle_add_source(activation_source_handle_cb, self);
priv->activation_func = func;
}
static void
activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->activation_idle_source) {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s",
_activation_func_to_string(func));
} else if (priv->activation_func == func) {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s (which was already scheduled)",
_activation_func_to_string(func));
} else {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s (which replaces %s)",
_activation_func_to_string(func),
_activation_func_to_string(priv->activation_func));
}
nm_clear_g_source_inst(&priv->activation_idle_source);
priv->activation_func = NULL;
func(self);
}
static void
activation_source_invoke_or_schedule(NMDevice *self, ActivationHandleFunc func, gboolean do_sync)
{
nm_assert(NM_IS_DEVICE(self));
nm_assert(NM_DEVICE_GET_PRIVATE(self)->act_request.obj);
nm_assert(func);
if (do_sync) {
activation_source_invoke_sync(self, func);
return;
}
activation_source_schedule(self, func);
}
/*****************************************************************************/
static void
master_ready(NMDevice *self, NMActiveConnection *active)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMActiveConnection *master_connection;
NMDevice *master;
/* Notify a master device that it has a new slave */
nm_assert(nm_active_connection_get_master_ready(active));
master_connection = nm_active_connection_get_master(active);
master = nm_active_connection_get_device(master_connection);
_LOGD(LOGD_DEVICE, "master connection ready; master device %s", nm_device_get_iface(master));
if (priv->master && priv->master != master)
nm_device_master_release_one_slave(priv->master,
self,
FALSE,
FALSE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
/* If the master didn't change, add-slave only rechecks whether to assume a connection. */
nm_device_master_add_slave(master,
self,
!nm_device_sys_iface_state_is_external_or_assume(self));
}
static void
master_ready_cb(NMActiveConnection *active, GParamSpec *pspec, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(nm_active_connection_get_master_ready(active));
if (priv->state == NM_DEVICE_STATE_PREPARE)
nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
}
static NMPlatformVF *
sriov_vf_config_to_platform(NMDevice *self, NMSriovVF *vf, GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free NMPlatformVF *plat_vf = NULL;
const guint *vlan_ids;
GVariant *variant;
guint i, num_vlans;
gsize length;
g_return_val_if_fail(!error || !*error, FALSE);
vlan_ids = nm_sriov_vf_get_vlan_ids(vf, &num_vlans);
plat_vf = g_malloc0(sizeof(NMPlatformVF) + sizeof(NMPlatformVFVlan) * num_vlans);
plat_vf->index = nm_sriov_vf_get_index(vf);
variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_SPOOF_CHECK);
if (variant)
plat_vf->spoofchk = g_variant_get_boolean(variant);
else
plat_vf->spoofchk = -1;
variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_TRUST);
if (variant)
plat_vf->trust = g_variant_get_boolean(variant);
else
plat_vf->trust = -1;
variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAC);
if (variant) {
if (!_nm_utils_hwaddr_aton(g_variant_get_string(variant, NULL),
plat_vf->mac.data,
sizeof(plat_vf->mac.data),
&length)) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"invalid MAC %s",
g_variant_get_string(variant, NULL));
return NULL;
}
if (length != priv->hw_addr_len) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"wrong MAC length %" G_GSIZE_FORMAT ", should be %u",
length,
priv->hw_addr_len);
return NULL;
}
plat_vf->mac.len = length;
}
variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MIN_TX_RATE);
if (variant)
plat_vf->min_tx_rate = g_variant_get_uint32(variant);
variant = nm_sriov_vf_get_attribute(vf, NM_SRIOV_VF_ATTRIBUTE_MAX_TX_RATE);
if (variant)
plat_vf->max_tx_rate = g_variant_get_uint32(variant);
plat_vf->num_vlans = num_vlans;
plat_vf->vlans = (NMPlatformVFVlan *) (&plat_vf[1]);
for (i = 0; i < num_vlans; i++) {
plat_vf->vlans[i].id = vlan_ids[i];
plat_vf->vlans[i].qos = nm_sriov_vf_get_vlan_qos(vf, vlan_ids[i]);
plat_vf->vlans[i].proto_ad =
nm_sriov_vf_get_vlan_protocol(vf, vlan_ids[i]) == NM_SRIOV_VF_VLAN_PROTOCOL_802_1AD;
}
return g_steal_pointer(&plat_vf);
}
static void
sriov_params_cb(GError *error, gpointer user_data)
{
NMDevice *self;
NMDevicePrivate *priv;
nm_auto_freev NMPlatformVF **plat_vfs = NULL;
nm_utils_user_data_unpack(user_data, &self, &plat_vfs);
if (nm_utils_error_is_cancelled_or_disposing(error))
return;
priv = NM_DEVICE_GET_PRIVATE(self);
if (error) {
_LOGE(LOGD_DEVICE, "failed to set SR-IOV parameters: %s", error->message);
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
return;
}
if (!nm_platform_link_set_sriov_vfs(nm_device_get_platform(self),
priv->ifindex,
(const NMPlatformVF *const *) plat_vfs)) {
_LOGE(LOGD_DEVICE, "failed to apply SR-IOV VFs");
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
return;
}
priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
}
/*
* activate_stage1_device_prepare
*
* Prepare for device activation
*
*/
static void
activate_stage1_device_prepare(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMActStageReturn ret = NM_ACT_STAGE_RETURN_SUCCESS;
NMActiveConnection *active;
NMActiveConnection *master;
NMDeviceClass *klass;
nm_assert((priv->ip_data_4.state == NM_DEVICE_IP_STATE_NONE)
== (priv->ip_data_6.state == NM_DEVICE_IP_STATE_NONE));
if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_NONE) {
_dev_ip_state_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING, "stage1");
_dev_ip_state_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_PENDING, "stage1");
/* Notify the new ActiveConnection along with the state change */
nm_dbus_track_obj_path_set(&priv->act_request, priv->act_request.obj, TRUE);
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = FALSE;
priv->l3config_merge_flags_has = FALSE;
}
nm_device_state_changed(self, NM_DEVICE_STATE_PREPARE, NM_DEVICE_STATE_REASON_NONE);
if (priv->stage1_sriov_state != NM_DEVICE_STAGE_STATE_COMPLETED) {
NMSettingSriov *s_sriov;
if (nm_device_sys_iface_state_is_external_or_assume(self)) {
/* pass */
} else if (priv->stage1_sriov_state == NM_DEVICE_STAGE_STATE_PENDING)
return;
else if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)
&& (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
nm_auto_freev NMPlatformVF **plat_vfs = NULL;
gs_free_error GError *error = NULL;
NMSriovVF *vf;
NMTernary autoprobe;
guint num;
guint i;
autoprobe = nm_setting_sriov_get_autoprobe_drivers(s_sriov);
if (autoprobe == NM_TERNARY_DEFAULT) {
autoprobe = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("sriov.autoprobe-drivers"),
self,
NM_OPTION_BOOL_FALSE,
NM_OPTION_BOOL_TRUE,
NM_OPTION_BOOL_TRUE);
}
num = nm_setting_sriov_get_num_vfs(s_sriov);
plat_vfs = g_new0(NMPlatformVF *, num + 1);
for (i = 0; i < num; i++) {
vf = nm_setting_sriov_get_vf(s_sriov, i);
plat_vfs[i] = sriov_vf_config_to_platform(self, vf, &error);
if (!plat_vfs[i]) {
_LOGE(LOGD_DEVICE,
"failed to apply SR-IOV VF '%s': %s",
nm_utils_sriov_vf_to_str(vf, FALSE, NULL),
error->message);
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED);
return;
}
}
/* When changing the number of VFs the kernel can block
* for very long time in the write to sysfs, especially
* if autoprobe-drivers is enabled. Do it asynchronously
* to avoid blocking the entire NM process.
*/
sriov_op_queue(self,
nm_setting_sriov_get_total_vfs(s_sriov),
NM_TERNARY_TO_OPTION_BOOL(autoprobe),
sriov_params_cb,
nm_utils_user_data_pack(self, g_steal_pointer(&plat_vfs)));
priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_PENDING;
return;
}
priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_COMPLETED;
}
/* Assumed connections were already set up outside NetworkManager */
klass = NM_DEVICE_GET_CLASS(self);
if (klass->act_stage1_prepare_set_hwaddr_ethernet
&& !nm_device_sys_iface_state_is_external_or_assume(self)) {
if (!nm_device_hw_addr_set_cloned(self, nm_device_get_applied_connection(self), FALSE)) {
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
}
if (klass->act_stage1_prepare_also_for_external_or_assume
|| !nm_device_sys_iface_state_is_external_or_assume(self)) {
nm_assert(!klass->act_stage1_prepare_also_for_external_or_assume
|| klass->act_stage1_prepare);
if (klass->act_stage1_prepare) {
NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
ret = klass->act_stage1_prepare(self, &failure_reason);
if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
return;
}
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
return;
nm_assert(ret == NM_ACT_STAGE_RETURN_SUCCESS);
}
}
active = NM_ACTIVE_CONNECTION(priv->act_request.obj);
master = nm_active_connection_get_master(active);
if (master) {
if (nm_active_connection_get_state(master) >= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
_LOGD(LOGD_DEVICE, "master connection is deactivating");
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED);
return;
}
/* If the master connection is ready for slaves, attach ourselves */
if (!nm_active_connection_get_master_ready(active)) {
if (priv->master_ready_id == 0) {
_LOGD(LOGD_DEVICE, "waiting for master connection to become ready");
priv->master_ready_id =
g_signal_connect(active,
"notify::" NM_ACTIVE_CONNECTION_INT_MASTER_READY,
G_CALLBACK(master_ready_cb),
self);
}
return;
}
}
nm_clear_g_signal_handler(priv->act_request.obj, &priv->master_ready_id);
if (master)
master_ready(self, active);
else if (priv->master) {
nm_device_master_release_one_slave(priv->master,
self,
TRUE,
TRUE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
nm_device_activate_schedule_stage2_device_config(self, TRUE);
}
void
nm_device_activate_schedule_stage1_device_prepare(NMDevice *self, gboolean do_sync)
{
activation_source_invoke_or_schedule(self, activate_stage1_device_prepare, do_sync);
}
static NMActStageReturn
act_stage2_config(NMDevice *self, NMDeviceStateReason *out_failure_reason)
{
return NM_ACT_STAGE_RETURN_SUCCESS;
}
static void
_lldp_neighbors_changed_cb(NMLldpListener *lldp_listener, gpointer user_data)
{
_notify(user_data, PROP_LLDP_NEIGHBORS);
}
static void
lldp_setup(NMDevice *self, NMTernary enabled)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int ifindex;
gboolean notify_lldp_neighbors = FALSE;
gboolean notify_interface_flags = FALSE;
ifindex = nm_device_get_ifindex(self);
if (ifindex <= 0)
enabled = FALSE;
else if (enabled == NM_TERNARY_DEFAULT)
enabled = _prop_get_connection_lldp(self);
if (priv->lldp_listener) {
if (!enabled || nm_lldp_listener_get_ifindex(priv->lldp_listener) != ifindex) {
nm_clear_pointer(&priv->lldp_listener, nm_lldp_listener_destroy);
notify_lldp_neighbors = TRUE;
}
}
if (enabled && !priv->lldp_listener) {
gs_free_error GError *error = NULL;
priv->lldp_listener =
nm_lldp_listener_new(ifindex, _lldp_neighbors_changed_cb, self, &error);
if (!priv->lldp_listener) {
/* This really shouldn't happen. It's likely a bug. Investigate when this happens! */
_LOGW(LOGD_DEVICE,
"LLDP listener for ifindex %d could not be started: %s",
ifindex,
error->message);
} else
notify_lldp_neighbors = TRUE;
}
notify_interface_flags = set_interface_flags(self,
NM_DEVICE_INTERFACE_FLAG_LLDP_CLIENT_ENABLED,
!!priv->lldp_listener,
FALSE);
nm_gobject_notify_together(self,
notify_lldp_neighbors ? PROP_LLDP_NEIGHBORS : PROP_0,
notify_interface_flags ? PROP_INTERFACE_FLAGS : PROP_0);
}
/* set-mode can be:
* - TRUE: sync with new rules.
* - FALSE: sync, but remove all rules (== flush)
* - DEFAULT: forget about all the rules that we previously tracked,
* but don't actually remove them. This is when quitting NM
* we want to keep the rules.
* The problem is, after restart of NM, the rule manager will
* no longer remember that NM added these rules and treat them
* as externally added ones. Don't restart NetworkManager if
* you care about that.
*/
static void
_routing_rules_sync(NMDevice *self, NMTernary set_mode)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPRouteManager *route_manager = nm_netns_get_route_manager(nm_device_get_netns(self));
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
gboolean untrack_only_dirty = FALSE;
gboolean keep_deleted_rules;
gpointer user_tag_1;
gpointer user_tag_2;
/* take two arbitrary user-tag pointers that belong to @self. */
user_tag_1 = &priv->v4_route_table;
user_tag_2 = &priv->v6_route_table;
if (set_mode == NM_TERNARY_TRUE) {
NMConnection *applied_connection;
NMSettingIPConfig *s_ip;
guint i, num;
int is_ipv4;
untrack_only_dirty = TRUE;
applied_connection = nm_device_get_applied_connection(self);
for (is_ipv4 = 0; applied_connection && is_ipv4 < 2; is_ipv4++) {
int addr_family = is_ipv4 ? AF_INET : AF_INET6;
s_ip = nm_connection_get_setting_ip_config(applied_connection, addr_family);
if (!s_ip)
continue;
num = nm_setting_ip_config_get_num_routing_rules(s_ip);
for (i = 0; i < num; i++) {
NMPlatformRoutingRule plrule;
NMIPRoutingRule *rule;
rule = nm_setting_ip_config_get_routing_rule(s_ip, i);
nm_ip_routing_rule_to_platform(rule, &plrule);
/* We track this rule, but we also make it explicitly not weakly-tracked
* (meaning to untrack NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
* the same time). */
nmp_route_manager_track_rule(route_manager,
&plrule,
10,
user_tag_1,
NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
}
}
if (klass->get_extra_rules) {
gs_unref_ptrarray GPtrArray *extra_rules = NULL;
extra_rules = klass->get_extra_rules(self);
if (extra_rules) {
for (i = 0; i < extra_rules->len; i++) {
nmp_route_manager_track_rule(
route_manager,
NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]),
10,
user_tag_2,
NMP_ROUTE_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
}
}
}
}
nmp_route_manager_untrack_all(route_manager, user_tag_1, !untrack_only_dirty, TRUE);
if (klass->get_extra_rules)
nmp_route_manager_untrack_all(route_manager, user_tag_2, !untrack_only_dirty, TRUE);
keep_deleted_rules = FALSE;
if (set_mode == NM_TERNARY_DEFAULT) {
/* when exiting NM, we leave the device up and the rules configured.
* We just call nmp_route_manager_sync() to forget about the synced rules,
* but we don't actually delete them.
*
* FIXME: that is a problem after restart of NetworkManager, because these
* rules will look like externally added, and NM will no longer remove
* them.
* To fix that, we could during "assume" mark the rules of the profile
* as owned (and "added" by the device). The problem with that is that it
* wouldn't cover rules that devices add by internal decision (not because
* of a setting in the profile, e.g. WireGuard could setup policy routing).
* Maybe it would be better to remember these orphaned rules at exit in a
* file and track them after restart again. */
keep_deleted_rules = TRUE;
}
nmp_route_manager_sync(route_manager, NMP_OBJECT_TYPE_ROUTING_RULE, keep_deleted_rules);
}
static gboolean
tc_commit(NMDevice *self)
{
gs_unref_ptrarray GPtrArray *qdiscs = NULL;
gs_unref_ptrarray GPtrArray *tfilters = NULL;
NMSettingTCConfig *s_tc;
NMPlatform *platform;
int ip_ifindex;
s_tc = nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG);
if (!s_tc)
return TRUE;
ip_ifindex = nm_device_get_ip_ifindex(self);
if (!ip_ifindex)
return FALSE;
platform = nm_device_get_platform(self);
qdiscs = nm_utils_qdiscs_from_tc_setting(platform, s_tc, ip_ifindex);
tfilters = nm_utils_tfilters_from_tc_setting(platform, s_tc, ip_ifindex);
if (!nm_platform_tc_sync(platform, ip_ifindex, qdiscs, tfilters))
return FALSE;
return TRUE;
}
/*
* activate_stage2_device_config
*
* Determine device parameters and set those on the device, ie
* for wireless devices, set SSID, keys, etc.
*
*/
static void
activate_stage2_device_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass;
NMActStageReturn ret;
NMSettingWired *s_wired;
gboolean no_firmware = FALSE;
CList *iter;
NMTernary accept_all_mac_addresses;
nm_device_state_changed(self, NM_DEVICE_STATE_CONFIG, NM_DEVICE_STATE_REASON_NONE);
if (!nm_device_sys_iface_state_is_external_or_assume(self))
_ethtool_state_set(self);
if (!nm_device_sys_iface_state_is_external_or_assume(self)) {
if (!priv->tc_committed && !tc_commit(self)) {
_LOGW(LOGD_DEVICE, "failed applying traffic control rules");
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
priv->tc_committed = TRUE;
}
_routing_rules_sync(self, NM_TERNARY_TRUE);
if (!nm_device_sys_iface_state_is_external_or_assume(self)) {
if (!nm_device_bring_up(self, FALSE, &no_firmware)) {
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
no_firmware ? NM_DEVICE_STATE_REASON_FIRMWARE_MISSING
: NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
}
klass = NM_DEVICE_GET_CLASS(self);
if (klass->act_stage2_config_also_for_external_or_assume
|| !nm_device_sys_iface_state_is_external_or_assume(self)) {
NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
ret = klass->act_stage2_config(self, &failure_reason);
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
return;
if (ret != NM_ACT_STAGE_RETURN_SUCCESS) {
nm_assert(ret == NM_ACT_STAGE_RETURN_FAILURE);
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
return;
}
}
/* If we have slaves that aren't yet enslaved, do that now */
c_list_for_each (iter, &priv->slaves) {
SlaveInfo *info = c_list_entry(iter, SlaveInfo, lst_slave);
NMDeviceState slave_state = nm_device_get_state(info->slave);
if (slave_state == NM_DEVICE_STATE_IP_CONFIG)
nm_device_master_enslave_slave(self,
info->slave,
nm_device_get_applied_connection(info->slave));
else if (priv->act_request.obj && nm_device_sys_iface_state_is_external(self)
&& slave_state <= NM_DEVICE_STATE_DISCONNECTED)
nm_device_queue_recheck_assume(info->slave);
}
s_wired = nm_device_get_applied_setting(self, NM_TYPE_SETTING_WIRED);
accept_all_mac_addresses =
s_wired ? nm_setting_wired_get_accept_all_mac_addresses(s_wired) : NM_TERNARY_DEFAULT;
if (accept_all_mac_addresses != NM_TERNARY_DEFAULT) {
int ifindex = nm_device_get_ip_ifindex(self);
if (ifindex > 0) {
int ifi_flags =
nm_platform_link_get_ifi_flags(nm_device_get_platform(self), ifindex, IFF_PROMISC);
if (ifi_flags >= 0 && ((!!ifi_flags) != (!!accept_all_mac_addresses))) {
nm_platform_link_change_flags(nm_device_get_platform(self),
ifindex,
IFF_PROMISC,
!!accept_all_mac_addresses);
if (priv->promisc_reset == NM_OPTION_BOOL_DEFAULT)
priv->promisc_reset = !accept_all_mac_addresses;
}
}
}
lldp_setup(self, NM_TERNARY_DEFAULT);
_commit_mtu(self);
nm_device_activate_schedule_stage3_ip_config(self, TRUE);
}
void
nm_device_activate_schedule_stage2_device_config(NMDevice *self, gboolean do_sync)
{
activation_source_invoke_or_schedule(self, activate_stage2_device_config, do_sync);
}
/*****************************************************************************/
static void
_dev_ipllx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipll_data_x[IS_IPv4].state != state) {
_LOGD_ipll(addr_family,
"set state %s (was %s)",
nm_device_ip_state_to_string(state),
nm_device_ip_state_to_string(priv->ipll_data_x[IS_IPv4].state));
priv->ipll_data_x[IS_IPv4].state = state;
}
}
static void
_dev_ipllx_cleanup(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (IS_IPv4) {
if (nm_clear_pointer(&priv->ipll_data_4.v4.ipv4ll, nm_l3_ipv4ll_unref))
nm_clear_pointer(&priv->ipll_data_4.v4.ipv4ll_registation,
nm_l3_ipv4ll_register_remove);
else
nm_assert(!priv->ipll_data_4.v4.ipv4ll_registation);
nm_clear_g_source_inst(&priv->ipll_data_4.v4.timeout_source);
} else {
nm_clear_pointer(&priv->ipll_data_6.v6.ipv6ll, nm_l3_ipv6ll_destroy);
priv->ipll_data_6.v6.llstate = NM_L3_IPV6LL_STATE_NONE;
priv->ipll_data_6.v6.lladdr = nm_ip_addr_zero.addr6;
nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
}
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_LL_X(IS_IPv4), NULL, FALSE);
_dev_ipllx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
}
/*****************************************************************************/
static void
_dev_ipll4_notify_event(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NML3IPv4LLState ipv4ll_state;
const NML3ConfigData *l3cd;
NMDeviceIPState state;
nm_assert(NM_IS_L3_IPV4LL(priv->ipll_data_4.v4.ipv4ll));
nm_assert(priv->ipll_data_4.state >= NM_DEVICE_IP_STATE_PENDING);
ipv4ll_state = nm_l3_ipv4ll_get_state(priv->ipll_data_4.v4.ipv4ll);
if (nm_l3_ipv4ll_state_is_good(ipv4ll_state)) {
l3cd = nm_l3_ipv4ll_get_l3cd(priv->ipll_data_4.v4.ipv4ll);
nm_assert(NM_IS_L3_CONFIG_DATA(l3cd));
nm_assert(!nm_l3_ipv4ll_is_timed_out(priv->ipll_data_4.v4.ipv4ll));
state = NM_DEVICE_IP_STATE_READY;
} else if (priv->ipll_data_4.v4.ipv4ll
&& nm_l3_ipv4ll_is_timed_out(priv->ipll_data_4.v4.ipv4ll)) {
l3cd = NULL;
state = NM_DEVICE_IP_STATE_FAILED;
} else {
l3cd = NULL;
state = (priv->ipll_data_4.state == NM_DEVICE_IP_STATE_PENDING) ? NM_DEVICE_IP_STATE_PENDING
: NM_DEVICE_IP_STATE_FAILED;
}
_dev_ipllx_set_state(self, AF_INET, state);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_LL_4, l3cd, FALSE);
_dev_ip_state_check_async(self, AF_INET);
}
static void
_dev_ipll4_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint32 timeout_msec;
if (priv->ipll_data_4.state >= NM_DEVICE_IP_STATE_PENDING)
return;
_dev_ipllx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING);
timeout_msec = _prop_get_ipv4_dad_timeout(self);
if (timeout_msec == 0)
timeout_msec = NM_ACD_TIMEOUT_RFC5227_MSEC;
priv->ipll_data_4.v4.ipv4ll = nm_l3cfg_access_ipv4ll(priv->l3cfg);
priv->ipll_data_4.v4.ipv4ll_registation =
nm_l3_ipv4ll_register_new(priv->ipll_data_4.v4.ipv4ll, timeout_msec);
}
/*****************************************************************************/
static const char *
_device_get_dhcp_anycast_address(NMDevice *self)
{
NMDeviceClass *klass;
nm_assert(NM_IS_DEVICE(self));
klass = NM_DEVICE_GET_CLASS(self);
if (klass->get_dhcp_anycast_address)
return klass->get_dhcp_anycast_address(self);
return NULL;
}
/*****************************************************************************/
static IPDevStateData *
_dev_ipdev_data(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
switch (addr_family) {
case AF_INET:
return &priv->ipdev_data_4;
case AF_INET6:
return &priv->ipdev_data_6;
default:
nm_assert_not_reached();
/* fall-through */
case AF_UNSPEC:
return &priv->ipdev_data_unspec;
}
}
static void
_dev_ipdev_cleanup(NMDevice *self, int addr_family)
{
IPDevStateData *p;
p = _dev_ipdev_data(self, addr_family);
if (p->state != NM_DEVICE_IP_STATE_NONE) {
_LOGD_ipdev(addr_family, "reset state");
p->state = NM_DEVICE_IP_STATE_NONE;
p->failed_reason = NM_DEVICE_STATE_REASON_NONE;
}
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DEVIP(addr_family), NULL, FALSE);
}
NMDeviceIPState
nm_device_devip_get_state(NMDevice *self, int addr_family)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_IP_STATE_NONE);
return _dev_ipdev_data(self, addr_family)->state;
}
void
nm_device_devip_set_state_full(NMDevice *self,
int addr_family,
NMDeviceIPState ip_state,
const NML3ConfigData *l3cd,
NMDeviceStateReason failed_reason)
{
NMDevicePrivate *priv;
IPDevStateData *p;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert_addr_family_or_unspec(addr_family);
nm_assert(NM_IN_SET(ip_state,
NM_DEVICE_IP_STATE_PENDING,
NM_DEVICE_IP_STATE_READY,
NM_DEVICE_IP_STATE_FAILED));
nm_assert(!l3cd || NM_IS_L3_CONFIG_DATA(l3cd));
nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED)
== (failed_reason == NM_DEVICE_STATE_REASON_NONE));
nm_assert((ip_state != NM_DEVICE_IP_STATE_FAILED) || !l3cd);
p = _dev_ipdev_data(self, addr_family);
if (p->state == ip_state && p->failed_reason == failed_reason
&& priv->l3cds[L3_CONFIG_DATA_TYPE_DEVIP(addr_family)].d == l3cd)
return;
if (ip_state == NM_DEVICE_IP_STATE_FAILED) {
_LOGD_ipdev(addr_family,
"set state=failed (reason %s)",
nm_device_state_reason_to_string_a(failed_reason));
} else {
_LOGD_ipdev(addr_family,
"set state=%s%s",
nm_device_ip_state_to_string(ip_state),
l3cd ? " (has extra IP configuration)" : "");
}
p->state = ip_state;
p->failed_reason = failed_reason;
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DEVIP(addr_family), l3cd, FALSE);
_dev_ip_state_check_async(self, addr_family);
}
/*****************************************************************************/
static void
_dev_ipmanual_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int IS_IPv4;
if (addr_family == AF_UNSPEC) {
_dev_ipmanual_set_state(self, AF_INET, state);
_dev_ipmanual_set_state(self, AF_INET6, state);
return;
}
IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipmanual_data.state_x[IS_IPv4] != state) {
_LOGD_ipmanual(addr_family, "set state %s", nm_device_ip_state_to_string(state));
priv->ipmanual_data.state_x[IS_IPv4] = state;
}
}
static void
_dev_ipmanual_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipmanual_data.state_4 == NM_DEVICE_IP_STATE_NONE
&& priv->ipmanual_data.state_6 == NM_DEVICE_IP_STATE_NONE) {
nm_assert(!priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d);
return;
}
_dev_ipmanual_set_state(self, AF_UNSPEC, NM_DEVICE_IP_STATE_NONE);
nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
priv->ipmanual_data.carrier_timeout_expired = FALSE;
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_MANUALIP, NULL, FALSE);
_dev_ip_state_check_async(self, AF_INET);
_dev_ip_state_check_async(self, AF_INET6);
}
static gboolean
_dev_ipmanual_carrier_timeout(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->ipmanual_data.carrier_timeout_expired = TRUE;
nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
_dev_ipmanual_check_ready(self);
return G_SOURCE_CONTINUE;
}
static void
_dev_ipmanual_check_ready(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NMPlatformLink *plink;
gboolean has_carrier;
NML3CfgCheckReadyFlags flags;
gboolean ready;
gboolean acd_used = FALSE;
int IS_IPv4;
if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_PENDING
&& priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_PENDING) {
/* we only care about PENDING to get it READY. Currently not other
* conditions are implemented. That is, we cannot get to FAILED
* (maybe we should, if DAD fails) and we cannot get from anything
* once we are READY. */
return;
}
plink = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
has_carrier = plink && NM_FLAGS_HAS(plink->n_ifi_flags, IFF_LOWER_UP);
if (has_carrier) {
nm_clear_g_source_inst(&priv->ipmanual_data.carrier_timeout);
} else {
if (priv->ipmanual_data.carrier_timeout_expired) {
/* go on */
} else if (priv->ipmanual_data.carrier_timeout) {
/* wait a bit more until timer expires */
return;
} else {
priv->ipmanual_data.carrier_timeout =
nm_g_timeout_add_source(2000, G_SOURCE_FUNC(_dev_ipmanual_carrier_timeout), self);
return;
}
}
flags = NM_L3CFG_CHECK_READY_FLAGS_NONE;
if (has_carrier) {
flags |= NM_L3CFG_CHECK_READY_FLAGS_IP4_ACD_READY;
flags |= NM_L3CFG_CHECK_READY_FLAGS_IP6_DAD_READY;
}
for (IS_IPv4 = 0; IS_IPv4 < 2; IS_IPv4++) {
const int addr_family = IS_IPv4 ? AF_INET : AF_INET6;
ready = nm_l3cfg_check_ready(priv->l3cfg,
priv->l3cds[L3_CONFIG_DATA_TYPE_MANUALIP].d,
addr_family,
flags,
&acd_used);
if (acd_used) {
_dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_UNSPEC);
} else if (ready) {
if (priv->ipmanual_data.state_x[IS_IPv4] != NM_DEVICE_IP_STATE_READY
&& nm_l3cfg_has_temp_not_available_obj(priv->l3cfg, addr_family)) {
/* Addresses with pending ACD/DAD are a possible cause for the
* presence of temporarily-not-available objects. Once all addresses
* are ready, retry to commit those unavailable objects. */
_dev_l3_cfg_commit(self, FALSE);
}
_dev_ipmanual_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_UNSPEC);
}
}
}
static void
_dev_ipmanual_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
if (priv->ipmanual_data.state_4 != NM_DEVICE_IP_STATE_NONE
|| priv->ipmanual_data.state_6 != NM_DEVICE_IP_STATE_NONE)
return;
if (nm_device_get_ip_ifindex(self) > 0) {
l3cd =
nm_device_create_l3_config_data_from_connection(self,
nm_device_get_applied_connection(self));
}
if (!l3cd) {
_dev_ipmanual_cleanup(self);
return;
}
/* Initially we set the state to pending, because we (maybe) have to perform ACD first. */
_dev_ipmanual_set_state(self, AF_UNSPEC, NM_DEVICE_IP_STATE_PENDING);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_MANUALIP, l3cd, FALSE);
_dev_ip_state_check_async(self, AF_UNSPEC);
}
/*****************************************************************************/
static void
_dev_ipdhcpx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipdhcp_data_x[IS_IPv4].state != state) {
_LOGD_ipdhcp(addr_family,
"set state %s (was %s)",
nm_device_ip_state_to_string(state),
nm_device_ip_state_to_string(priv->ipdhcp_data_x[IS_IPv4].state));
priv->ipdhcp_data_x[IS_IPv4].state = state;
}
}
static void
_dev_ipdhcpx_cleanup(NMDevice *self, int addr_family, gboolean full_cleanup, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
_dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
if (full_cleanup && !IS_IPv4) {
priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_NONE;
priv->ipdhcp_data_6.v6.needed_prefixes = 0;
}
if (full_cleanup)
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), NULL, FALSE);
if (priv->ipdhcp_data_x[IS_IPv4].client) {
nm_clear_g_signal_handler(priv->ipdhcp_data_x[IS_IPv4].client,
&priv->ipdhcp_data_x[IS_IPv4].notify_sigid);
nm_dhcp_client_stop(priv->ipdhcp_data_x[IS_IPv4].client, release);
g_clear_object(&priv->ipdhcp_data_x[IS_IPv4].client);
}
if (full_cleanup && priv->ipdhcp_data_x[IS_IPv4].config) {
gs_unref_object NMDhcpConfig *config =
g_steal_pointer(&priv->ipdhcp_data_x[IS_IPv4].config);
_notify(self, PROP_DHCPX_CONFIG(IS_IPv4));
nm_dbus_object_unexport_on_idle(g_steal_pointer(&config));
}
_dev_ip_state_check_async(self, addr_family);
}
static void
_dev_ipdhcpx_handle_fail(NMDevice *self, int addr_family, const char *reason)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipdhcp_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_FAILED)
return;
_LOGT_ipdhcp(addr_family, "DHCP failing: %s", reason ?: "unknown reason");
_dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_FAILED);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4), NULL, FALSE);
if (priv->ipdhcp_data_x[IS_IPv4].config)
nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config, NULL);
_dev_ip_state_check_async(self, addr_family);
}
static void
_dev_ipdhcpx_notify(NMDhcpClient *client, const NMDhcpClientNotifyData *notify_data, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int addr_family = nm_dhcp_client_get_addr_family(client);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
nm_assert(notify_data);
nm_assert(priv->ipdhcp_data_x[IS_IPv4].state > NM_DEVICE_IP_STATE_NONE);
nm_assert(client && priv->ipdhcp_data_x[IS_IPv4].client == client);
switch (notify_data->notify_type) {
case NM_DHCP_CLIENT_NOTIFY_TYPE_PREFIX_DELEGATED:
nm_assert(!IS_IPv4);
/* Just re-emit. The device just contributes the prefix to the
* pool in NMPolicy, which decides about subnet allocation
* on the shared devices. */
g_signal_emit(self, signals[IP6_PREFIX_DELEGATED], 0, notify_data->prefix_delegated.prefix);
return;
case NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT:
/* Here we also fail if we had a lease and it expired. Maybe,
* ipv[46].dhcp-timeout should only cover the time until we get
* a lease for the first time. How it is here, it means that a
* connection can fail after being connected successfully for a
* longer time. */
_dev_ipdhcpx_handle_fail(self, addr_family, "timeout getting lease");
return;
case NM_DHCP_CLIENT_NOTIFY_TYPE_IT_LOOKS_BAD:
/* Like NM_DHCP_CLIENT_NOTIFY_TYPE_NO_LEASE_TIMEOUT, this does not
* apply only if we never got a lease, but also after being fully
* connected. We can also fail then. */
_dev_ipdhcpx_handle_fail(self, addr_family, notify_data->it_looks_bad.reason);
return;
case NM_DHCP_CLIENT_NOTIFY_TYPE_LEASE_UPDATE:
if (!notify_data->lease_update.l3cd) {
_LOGT_ipdhcp(addr_family, "lease lost");
goto lease_update_out;
}
if (notify_data->lease_update.accepted)
_LOGT_ipdhcp(addr_family, "lease accepted");
else
_LOGT_ipdhcp(addr_family, "lease update");
nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config,
notify_data->lease_update.l3cd);
/* Schedule a commit of the configuration. If the DHCP client
* needs to accept the lease, it will send later a LEASE_UPDATE
* notification with accepted=1 once the address appears in platform.
* Otherwise, this notification already has accepted=1. */
_dev_l3_register_l3cds_set_one_full(self,
L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
notify_data->lease_update.l3cd,
NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
FALSE);
if (notify_data->lease_update.accepted) {
if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_READY) {
_dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_READY);
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP_CHANGE_X(IS_IPv4),
self,
NULL,
NULL,
NULL,
NULL);
_dev_ip_state_check_async(self, addr_family);
}
}
lease_update_out:
nm_device_update_metered(self);
return;
}
nm_assert_not_reached();
}
/*****************************************************************************/
static void
_dev_ipdhcpx_start(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
NMSettingConnection *s_con;
NMSettingIPConfig *s_ip;
const NML3ConfigData *previous_lease;
gs_unref_bytes GBytes *hwaddr = NULL;
gboolean enforce_duid = FALSE;
gs_free_error GError *error = NULL;
const NMPlatformLink *pllink;
guint no_lease_timeout_sec;
int ifindex;
const char *str;
gboolean request_broadcast;
const char *fail_reason;
if (priv->ipdhcp_data_x[IS_IPv4].state == NM_DEVICE_IP_STATE_NONE)
_dev_ipdhcpx_set_state(self, addr_family, NM_DEVICE_IP_STATE_PENDING);
else if (priv->ipdhcp_data_x[IS_IPv4].state > NM_DEVICE_IP_STATE_PENDING) {
/* already succeeded or failed */
return;
} else if (priv->ipdhcp_data_x[IS_IPv4].client) {
/* DHCP client already started */
return;
}
if (nm_device_sys_iface_state_is_external(self)) {
fail_reason = nm_assert_unreachable_val("cannot run DHCP on external interface");
goto out_fail;
}
connection = nm_device_get_applied_connection(self);
if (!connection) {
fail_reason = nm_assert_unreachable_val("no applied connection for starting DHCP");
goto out_fail;
}
s_con = nm_connection_get_setting_connection(connection);
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
nm_assert(s_con);
nm_assert(s_ip);
ifindex = 0;
pllink = nm_l3cfg_get_pllink(priv->l3cfg, TRUE);
if (pllink) {
ifindex = pllink->ifindex;
nm_assert(ifindex > 0);
nm_assert(ifindex == nm_device_get_ip_ifindex(self));
}
if (ifindex <= 0) {
fail_reason = "cannot start DHCP without interface";
goto out_fail;
}
hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
if (!IS_IPv4) {
if (!hwaddr) {
fail_reason = "interface has no MAC address to start DHCPv6";
goto out_fail;
}
}
request_broadcast = FALSE;
if (pllink) {
str = nmp_object_link_udev_device_get_property_value(NMP_OBJECT_UP_CAST(pllink),
"ID_NET_DHCP_BROADCAST");
if (str && _nm_utils_ascii_str_to_bool(str, FALSE)) {
/* Use the device property ID_NET_DHCP_BROADCAST setting, which may be set for interfaces
* requiring that the DHCPOFFER message is being broadcast because they can't handle unicast
* messages while not fully configured.
*/
request_broadcast = TRUE;
}
}
if (!IS_IPv4
&& NM_IN_SET(priv->ipll_data_6.state,
NM_DEVICE_IP_STATE_NONE,
NM_DEVICE_IP_STATE_PENDING)) {
_dev_ipll6_start(self);
return;
}
no_lease_timeout_sec = _prop_get_ipvx_dhcp_timeout(self, addr_family);
if (IS_IPv4) {
NMDhcpClientConfig config;
gs_unref_bytes GBytes *bcast_hwaddr = NULL;
gs_unref_bytes GBytes *client_id = NULL;
gs_unref_bytes GBytes *vendor_class_identifier = NULL;
const char *const *reject_servers;
const char *hostname;
gboolean hostname_is_fqdn;
client_id = _prop_get_ipv4_dhcp_client_id(self, connection, hwaddr);
vendor_class_identifier =
_prop_get_ipv4_dhcp_vendor_class_identifier(self, NM_SETTING_IP4_CONFIG(s_ip));
reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip, NULL);
bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
hostname = nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip));
if (hostname) {
hostname_is_fqdn = TRUE;
} else {
hostname_is_fqdn = FALSE;
hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip);
}
config = (NMDhcpClientConfig){
.addr_family = AF_INET,
.l3cfg = nm_device_get_l3cfg(self),
.iface = nm_device_get_ip_iface(self),
.uuid = nm_connection_get_uuid(connection),
.hwaddr = hwaddr,
.bcast_hwaddr = bcast_hwaddr,
.send_hostname = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
.hostname = hostname,
.hostname_flags = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
.client_id = client_id,
.mud_url = _prop_get_connection_mud_url(self, s_con),
.timeout = no_lease_timeout_sec,
.anycast_address = _device_get_dhcp_anycast_address(self),
.vendor_class_identifier = vendor_class_identifier,
.use_fqdn = hostname_is_fqdn,
.reject_servers = reject_servers,
.v4.request_broadcast = request_broadcast,
};
priv->ipdhcp_data_4.client =
nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error);
} else {
gs_unref_bytes GBytes *duid = NULL;
gboolean iaid_explicit;
guint32 iaid;
NMDhcpClientConfig config;
iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, FALSE, &iaid_explicit);
duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
config = (NMDhcpClientConfig){
.addr_family = AF_INET6,
.l3cfg = nm_device_get_l3cfg(self),
.iface = nm_device_get_ip_iface(self),
.uuid = nm_connection_get_uuid(connection),
.send_hostname = nm_setting_ip_config_get_dhcp_send_hostname(s_ip),
.hostname = nm_setting_ip_config_get_dhcp_hostname(s_ip),
.hostname_flags = _prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
.client_id = duid,
.mud_url = _prop_get_connection_mud_url(self, s_con),
.timeout = no_lease_timeout_sec,
.anycast_address = _device_get_dhcp_anycast_address(self),
.v6.enforce_duid = enforce_duid,
.v6.iaid = iaid,
.v6.iaid_explicit = iaid_explicit,
.v6.info_only = (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF),
.v6.needed_prefixes = priv->ipdhcp_data_6.v6.needed_prefixes,
};
priv->ipdhcp_data_6.client =
nm_dhcp_manager_start_client(nm_dhcp_manager_get(), &config, &error);
}
if (!priv->ipdhcp_data_x[IS_IPv4].client) {
fail_reason = error->message;
goto out_fail;
}
priv->ipdhcp_data_x[IS_IPv4].notify_sigid =
g_signal_connect(priv->ipdhcp_data_x[IS_IPv4].client,
NM_DHCP_CLIENT_NOTIFY,
G_CALLBACK(_dev_ipdhcpx_notify),
self);
/* FIXME(l3cfg:dhcp:previous-lease): take the NML3ConfigData from the previous lease (if any)
* and pass it on to NMDhcpClient. This is a fake lease that we use initially (until
* NMDhcpClient got a real lease). Note that NMDhcpClient needs to check whether the
* lease already expired. */
previous_lease = nm_dhcp_client_get_lease(priv->ipdhcp_data_x[IS_IPv4].client);
if (!priv->ipdhcp_data_x[IS_IPv4].config) {
priv->ipdhcp_data_x[IS_IPv4].config = nm_dhcp_config_new(addr_family, previous_lease);
_notify(self, PROP_DHCPX_CONFIG(IS_IPv4));
}
if (previous_lease) {
nm_dhcp_config_set_lease(priv->ipdhcp_data_x[IS_IPv4].config, previous_lease);
_dev_l3_register_l3cds_set_one_full(self,
L3_CONFIG_DATA_TYPE_DHCP_X(IS_IPv4),
previous_lease,
NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
FALSE);
}
return;
out_fail:
_dev_ipdhcpx_handle_fail(self, addr_family, fail_reason);
}
static void
_dev_ipdhcpx_start_continue(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_NONE)
_dev_ipdhcpx_start(self, addr_family);
}
static void
_dev_ipdhcpx_restart(NMDevice *self, int addr_family, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipdhcp_data_x[IS_IPv4].state != NM_DEVICE_IP_STATE_NONE) {
_LOGI_ipdhcp(addr_family, "restarting%s", release ? " (release lease)" : "");
_dev_ipdhcpx_cleanup(self, addr_family, FALSE, release);
}
_dev_ipdhcpx_start(self, addr_family);
}
void
nm_device_ip_method_dhcp4_start(NMDevice *self)
{
_dev_ipdhcpx_start(self, AF_INET);
}
static void
_dev_ipdhcp6_set_dhcp_level(NMDevice *self, NMNDiscDHCPLevel dhcp_level)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(NM_IN_SET(dhcp_level,
NM_NDISC_DHCP_LEVEL_NONE,
NM_NDISC_DHCP_LEVEL_OTHERCONF,
NM_NDISC_DHCP_LEVEL_MANAGED));
if (dhcp_level == NM_NDISC_DHCP_LEVEL_NONE && priv->ipdhcp_data_6.v6.needed_prefixes > 0)
dhcp_level = NM_NDISC_DHCP_LEVEL_OTHERCONF;
if (priv->ipdhcp_data_6.v6.mode == dhcp_level)
return;
_LOGD_ipdhcp(AF_INET6, "level: set to %s", nm_ndisc_dhcp_level_to_string(dhcp_level));
if (dhcp_level == NM_NDISC_DHCP_LEVEL_NONE) {
_dev_ipdhcpx_cleanup(self, AF_INET6, TRUE, TRUE);
return;
}
priv->ipdhcp_data_6.v6.mode = dhcp_level;
_dev_ipdhcpx_restart(self, AF_INET6, TRUE);
}
/*
* Called on the requesting interface when a subnet can't be obtained
* from known prefixes for a newly active shared connection.
*/
void
nm_device_request_ip6_prefixes(NMDevice *self, guint needed_prefixes)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipdhcp_data_6.v6.needed_prefixes == needed_prefixes)
return;
_LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %u prefixes", needed_prefixes);
priv->ipdhcp_data_6.v6.needed_prefixes = needed_prefixes;
if (priv->ipdhcp_data_6.v6.mode == NM_NDISC_DHCP_LEVEL_NONE) {
priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_OTHERCONF;
_LOGD_ipdhcp(AF_INET6,
"level: set to %s",
nm_ndisc_dhcp_level_to_string(NM_NDISC_DHCP_LEVEL_OTHERCONF));
}
_dev_ipdhcpx_restart(self, AF_INET6, TRUE);
}
/*****************************************************************************/
static gboolean
connection_ip_method_requires_carrier(NMConnection *connection,
int addr_family,
gboolean *out_ip_enabled)
{
const char *method;
method = nm_utils_get_ip_config_method(connection, addr_family);
if (NM_IS_IPv4(addr_family)) {
NM_SET_OUT(out_ip_enabled, !nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED));
return NM_IN_STRSET(method,
NM_SETTING_IP4_CONFIG_METHOD_AUTO,
NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL);
}
NM_SET_OUT(out_ip_enabled,
!NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED));
return NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NM_SETTING_IP6_CONFIG_METHOD_DHCP,
NM_SETTING_IP6_CONFIG_METHOD_SHARED,
NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL);
}
static gboolean
connection_requires_carrier(NMConnection *connection)
{
NMSettingIPConfig *s_ip4, *s_ip6;
NMSettingConnection *s_con;
gboolean ip4_carrier_wanted, ip6_carrier_wanted;
gboolean ip4_used = FALSE, ip6_used = FALSE;
/* We can progress to IP_CONFIG now, so that we're enslaved.
* That may actually cause carrier to go up and thus continue activation. */
s_con = nm_connection_get_setting_connection(connection);
if (nm_setting_connection_get_master(s_con))
return FALSE;
ip4_carrier_wanted = connection_ip_method_requires_carrier(connection, AF_INET, &ip4_used);
if (ip4_carrier_wanted) {
/* If IPv4 wants a carrier and cannot fail, the whole connection
* requires a carrier regardless of the IPv6 method.
*/
s_ip4 = nm_connection_get_setting_ip4_config(connection);
if (s_ip4 && !nm_setting_ip_config_get_may_fail(s_ip4))
return TRUE;
}
ip6_carrier_wanted = connection_ip_method_requires_carrier(connection, AF_INET6, &ip6_used);
if (ip6_carrier_wanted) {
/* If IPv6 wants a carrier and cannot fail, the whole connection
* requires a carrier regardless of the IPv4 method.
*/
s_ip6 = nm_connection_get_setting_ip6_config(connection);
if (s_ip6 && !nm_setting_ip_config_get_may_fail(s_ip6))
return TRUE;
}
/* If an IP version wants a carrier and the other IP version isn't
* used, the connection requires carrier since it will just fail without one.
*/
if (ip4_carrier_wanted && !ip6_used)
return TRUE;
if (ip6_carrier_wanted && !ip4_used)
return TRUE;
/* If both want a carrier, the whole connection wants a carrier */
return ip4_carrier_wanted && ip6_carrier_wanted;
}
static gboolean
have_any_ready_slaves(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
SlaveInfo *info;
CList *iter;
/* Any enslaved slave is "ready" in the generic case as it's
* at least >= NM_DEVCIE_STATE_IP_CONFIG and has had Layer 2
* properties set up.
*/
c_list_for_each (iter, &priv->slaves) {
info = c_list_entry(iter, SlaveInfo, lst_slave);
if (NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved)
return TRUE;
}
return FALSE;
}
/*****************************************************************************/
gboolean
nm_device_needs_ip6_subnet(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->needs_ip6_subnet;
}
/*
* Called on the ipv6.method=shared interface when a new subnet is allocated
* or the prefix from which it is allocated is renewed.
*/
void
nm_device_use_ip6_subnet(NMDevice *self, const NMPlatformIP6Address *subnet)
{
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
char sbuf[sizeof(_nm_utils_to_string_buffer)];
NMPlatformIP6Address address;
l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
/* Assign a ::1 address in the subnet for us. */
address = *subnet;
address.address.s6_addr32[3] |= htonl(1);
nm_l3_config_data_add_address_6(l3cd, &address);
_LOGD(LOGD_IP6,
"ipv6-pd: using %s",
nm_platform_ip6_address_to_string(&address, sbuf, sizeof(sbuf)));
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_PD_6, l3cd, FALSE);
_dev_l3_cfg_commit(self, TRUE);
_dev_ipac6_ndisc_set_router_config(self);
}
/*
* Called whenever the policy picks a default IPv6 device.
* The ipv6.method=shared devices just reuse its DNS configuration.
*/
void
nm_device_copy_ip6_dns_config(NMDevice *self, NMDevice *from_device)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDevicePrivate *priv_src;
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
const NML3ConfigData *l3cd_src = NULL;
/* FIXME(l3cfg): this entire code an approach seems flawed. It's flawed, because the
* very next RA will reset the changes. */
if (priv->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d) {
l3cd = nm_l3_config_data_new_clone(priv->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d, 0);
nm_l3_config_data_clear_nameservers(l3cd, AF_INET6);
nm_l3_config_data_clear_searches(l3cd, AF_INET6);
} else
l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
if (from_device) {
priv_src = NM_DEVICE_GET_PRIVATE(from_device);
l3cd_src = priv_src->l3cds[L3_CONFIG_DATA_TYPE_AC_6].d;
}
if (l3cd_src) {
const char *const *strvarr;
const struct in6_addr *addrs;
guint n;
guint i;
addrs = nm_l3_config_data_get_nameservers(l3cd_src, AF_INET6, &n);
for (i = 0; i < n; i++)
nm_l3_config_data_add_nameserver(l3cd, AF_INET6, &addrs[i]);
strvarr = nm_l3_config_data_get_searches(l3cd_src, AF_INET6, &n);
for (i = 0; i < n; i++)
nm_l3_config_data_add_search(l3cd, AF_INET6, strvarr[i]);
}
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_AC_6, l3cd, FALSE);
_dev_l3_cfg_commit(self, TRUE);
}
/*****************************************************************************/
static gboolean
_dev_ipll6_state_retry_cb(gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
_dev_ipll6_start(self);
return G_SOURCE_CONTINUE;
}
static void
_dev_ipll6_set_llstate(NMDevice *self, NML3IPv6LLState llstate, const struct in6_addr *lladdr)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean changed = FALSE;
NMDeviceIPState state;
NMDeviceIPState old_state;
if (!lladdr)
lladdr = &nm_ip_addr_zero.addr6;
if (priv->ipll_data_6.v6.llstate != llstate
|| !IN6_ARE_ADDR_EQUAL(&priv->ipll_data_6.v6.lladdr, lladdr)) {
changed = TRUE;
priv->ipll_data_6.v6.llstate = llstate;
priv->ipll_data_6.v6.lladdr = *lladdr;
}
nm_assert((priv->ipll_data_6.v6.ipv6ll
&& NM_IN_SET(priv->ipll_data_6.v6.llstate,
NM_L3_IPV6LL_STATE_STARTING,
NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS,
NM_L3_IPV6LL_STATE_READY,
NM_L3_IPV6LL_STATE_DAD_FAILED))
|| (!priv->ipll_data_6.v6.ipv6ll
&& NM_IN_SET(priv->ipll_data_6.v6.llstate,
NM_L3_IPV6LL_STATE_NONE,
NM_L3_IPV6LL_STATE_DEFUNCT)));
switch (priv->ipll_data_6.v6.llstate) {
case NM_L3_IPV6LL_STATE_NONE:
state = NM_DEVICE_IP_STATE_NONE;
break;
case NM_L3_IPV6LL_STATE_DEFUNCT:
case NM_L3_IPV6LL_STATE_DAD_FAILED:
state = NM_DEVICE_IP_STATE_FAILED;
break;
case NM_L3_IPV6LL_STATE_READY:
state = NM_DEVICE_IP_STATE_READY;
break;
case NM_L3_IPV6LL_STATE_STARTING:
case NM_L3_IPV6LL_STATE_DAD_IN_PROGRESS:
state = NM_DEVICE_IP_STATE_PENDING;
break;
default:
state = nm_assert_unreachable_val(NM_DEVICE_IP_STATE_FAILED);
break;
}
old_state = priv->ipll_data_6.state;
if (priv->ipll_data_6.state != state) {
priv->ipll_data_6.state = state;
changed = TRUE;
}
if (priv->ipll_data_6.v6.llstate != NM_L3_IPV6LL_STATE_DEFUNCT)
nm_clear_g_source_inst(&priv->ipll_data_6.v6.retry_source);
else if (!priv->ipll_data_6.v6.retry_source) {
/* we schedule a timer to try to recover from this... Possibly some higher layer
* will however fail the activation... */
priv->ipll_data_6.v6.retry_source =
nm_g_timeout_add_source(10000, _dev_ipll6_state_retry_cb, self);
}
if (changed) {
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
_LOGT_ipll(AF_INET6,
"set state %s (was %s, llstate=%s, lladdr=%s)",
nm_device_ip_state_to_string(priv->ipll_data_6.state),
nm_device_ip_state_to_string(old_state),
nm_l3_ipv6ll_state_to_string(priv->ipll_data_6.v6.llstate),
nm_ip_addr_is_null(AF_INET6, &priv->ipll_data_6.v6.lladdr)
? "(none)"
: _nm_utils_inet6_ntop(&priv->ipll_data_6.v6.lladdr, sbuf));
}
if (changed)
_dev_ip_state_check_async(self, AF_INET6);
if (priv->ipll_data_6.v6.llstate == NM_L3_IPV6LL_STATE_READY) {
/* if we got an IPv6LL address, we might poke some other methods
* to progress... */
_dev_ipac6_start_continue(self);
_dev_ipdhcpx_start_continue(self, AF_INET6);
}
}
static void
_dev_ipll6_state_change_cb(NML3IPv6LL *ipv6ll,
NML3IPv6LLState llstate,
const struct in6_addr *lladdr,
gpointer user_data)
{
_dev_ipll6_set_llstate(user_data, llstate, lladdr);
}
static void
_dev_ipll6_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
NMSettingIP6Config *s_ip6 = NULL;
gboolean assume;
const char *ifname;
NML3IPv6LLState llstate;
const struct in6_addr *lladdr;
if (priv->ipll_data_6.v6.ipv6ll)
return;
if (!priv->l3cfg) {
_LOGD(LOGD_IP6, "linklocal6: no IP link for IPv6");
goto out_fail;
}
ifname = nm_device_get_ip_iface(self);
if (!ifname) {
_LOGD(LOGD_IP6, "linklocal6: no interface name for IPv6");
goto out_fail;
}
connection = nm_device_get_applied_connection(self);
if (connection)
s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
assume = nm_device_sys_iface_state_is_external_or_assume(self);
if (s_ip6
&& nm_setting_ip6_config_get_addr_gen_mode(s_ip6)
== NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_STABLE_PRIVACY) {
NMUtilsStableType stable_type;
const char *stable_id;
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
priv->ipll_data_6.v6.ipv6ll =
nm_l3_ipv6ll_new_stable_privacy(priv->l3cfg,
assume,
stable_type,
ifname,
stable_id,
nm_device_get_route_table(self, AF_INET6),
_dev_ipll6_state_change_cb,
self);
} else {
NMUtilsIPv6IfaceId iid;
if (!nm_device_get_ip_iface_identifier(self, &iid, TRUE, NULL)) {
_LOGW(LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
goto out_fail;
}
priv->ipll_data_6.v6.ipv6ll =
nm_l3_ipv6ll_new_token(priv->l3cfg,
assume,
&iid,
nm_device_get_route_table(self, AF_INET6),
_dev_ipll6_state_change_cb,
self);
}
llstate = nm_l3_ipv6ll_get_state(priv->ipll_data_6.v6.ipv6ll, &lladdr);
_dev_ipll6_set_llstate(self, llstate, lladdr);
return;
out_fail:
_dev_ipll6_set_llstate(self, NM_L3_IPV6LL_STATE_DEFUNCT, NULL);
}
/*****************************************************************************/
gint64
nm_device_get_configured_mtu_from_connection_default(NMDevice *self,
const char *property_name,
guint32 max_mtu)
{
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
property_name,
self,
0,
max_mtu,
-1);
}
guint32
nm_device_get_configured_mtu_from_connection(NMDevice *self,
GType setting_type,
NMDeviceMtuSource *out_source)
{
const char *global_property_name;
NMConnection *connection;
NMSetting *setting;
gint64 mtu_default;
guint32 mtu = 0;
guint32 max_mtu = G_MAXUINT32;
nm_assert(NM_IS_DEVICE(self));
nm_assert(out_source);
connection = nm_device_get_applied_connection(self);
if (!connection)
g_return_val_if_reached(0);
setting = nm_connection_get_setting(connection, setting_type);
if (setting_type == NM_TYPE_SETTING_WIRED) {
if (setting)
mtu = nm_setting_wired_get_mtu(NM_SETTING_WIRED(setting));
global_property_name = NM_CON_DEFAULT("ethernet.mtu");
} else if (setting_type == NM_TYPE_SETTING_WIRELESS) {
if (setting)
mtu = nm_setting_wireless_get_mtu(NM_SETTING_WIRELESS(setting));
global_property_name = NM_CON_DEFAULT("wifi.mtu");
} else if (setting_type == NM_TYPE_SETTING_INFINIBAND) {
if (setting)
mtu = nm_setting_infiniband_get_mtu(NM_SETTING_INFINIBAND(setting));
global_property_name = NM_CON_DEFAULT("infiniband.mtu");
max_mtu = NM_INFINIBAND_MAX_MTU;
} else if (setting_type == NM_TYPE_SETTING_IP_TUNNEL) {
if (setting)
mtu = nm_setting_ip_tunnel_get_mtu(NM_SETTING_IP_TUNNEL(setting));
global_property_name = NM_CON_DEFAULT("ip-tunnel.mtu");
} else if (setting_type == NM_TYPE_SETTING_WIREGUARD) {
if (setting)
mtu = nm_setting_wireguard_get_mtu(NM_SETTING_WIREGUARD(setting));
global_property_name = NM_CON_DEFAULT("wireguard.mtu");
} else
g_return_val_if_reached(0);
if (mtu) {
*out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
return mtu;
}
mtu_default =
nm_device_get_configured_mtu_from_connection_default(self, global_property_name, max_mtu);
if (mtu_default >= 0) {
*out_source = NM_DEVICE_MTU_SOURCE_CONNECTION;
return (guint32) mtu_default;
}
*out_source = NM_DEVICE_MTU_SOURCE_NONE;
return 0;
}
guint32
nm_device_get_configured_mtu_for_wired(NMDevice *self,
NMDeviceMtuSource *out_source,
gboolean *out_force)
{
return nm_device_get_configured_mtu_from_connection(self, NM_TYPE_SETTING_WIRED, out_source);
}
guint32
nm_device_get_configured_mtu_wired_parent(NMDevice *self,
NMDeviceMtuSource *out_source,
gboolean *out_force)
{
guint32 mtu = 0;
guint32 parent_mtu = 0;
int ifindex;
ifindex = nm_device_parent_get_ifindex(self);
if (ifindex > 0) {
parent_mtu = nm_platform_link_get_mtu(nm_device_get_platform(self), ifindex);
if (parent_mtu >= NM_DEVICE_GET_CLASS(self)->mtu_parent_delta)
parent_mtu -= NM_DEVICE_GET_CLASS(self)->mtu_parent_delta;
else
parent_mtu = 0;
}
mtu = nm_device_get_configured_mtu_for_wired(self, out_source, NULL);
if (parent_mtu && mtu > parent_mtu) {
/* Trying to set a MTU that is out of range from configuration:
* fall back to the parent MTU and set force flag so that it
* overrides an MTU with higher priority already configured.
*/
*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
*out_force = TRUE;
return parent_mtu;
}
if (*out_source != NM_DEVICE_MTU_SOURCE_NONE) {
nm_assert(mtu > 0);
return mtu;
}
/* Inherit the MTU from parent device, if any */
if (parent_mtu) {
mtu = parent_mtu;
*out_source = NM_DEVICE_MTU_SOURCE_PARENT;
}
return mtu;
}
/*****************************************************************************/
static void
_set_mtu(NMDevice *self, guint32 mtu)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->mtu == mtu)
return;
priv->mtu = mtu;
_notify(self, PROP_MTU);
if (priv->master) {
/* changing the MTU of a slave, might require the master to reset
* its MTU. Note that the master usually cannot set a MTU larger
* then the slave's. Hence, when the slave increases the MTU,
* master might want to retry setting the MTU. */
nm_device_commit_mtu(priv->master);
}
}
static gboolean
set_platform_mtu(NMDevice *self, guint32 mtu)
{
int r;
r = nm_platform_link_set_mtu(nm_device_get_platform(self), nm_device_get_ip_ifindex(self), mtu);
return (r != -NME_PL_CANT_SET_MTU);
}
static void
_commit_mtu(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceMtuSource source = NM_DEVICE_MTU_SOURCE_NONE;
const NML3ConfigData *l3cd;
guint32 ip6_mtu_orig;
guint32 ip6_mtu;
guint32 mtu_desired_orig;
guint32 mtu_desired;
guint32 mtu_plat;
struct {
gboolean initialized;
guint32 value;
} ip6_mtu_sysctl = {
0,
};
int ifindex;
char sbuf[64];
char sbuf1[64];
char sbuf2[64];
gboolean success = TRUE;
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
return;
if (!nm_device_get_applied_connection(self)
|| nm_device_sys_iface_state_is_external_or_assume(self)) {
/* we don't tamper with the MTU of disconnected and
* external/assumed devices. */
return;
}
l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, FALSE);
{
guint32 mtu = 0;
guint32 mtu2;
gboolean force = FALSE;
/* We take the MTU from various sources: (in order of increasing
* priority) parent link, IP configuration (which contains the
* MTU from DHCP/PPP), connection profile.
*
* We could just compare it with the platform MTU and apply it
* when different, but this would revert at random times manual
* changes done by the user with the MTU from the connection.
*
* Instead, we remember the source of the currently configured
* MTU and apply the new one only when the new source has a
* higher priority, so that we don't set a MTU from same source
* multiple times. An exception to this is for the PARENT
* source, since we need to keep tracking the parent MTU when it
* changes.
*
* The subclass can set the @force argument to TRUE to signal that the
* returned MTU should be applied even if it has a lower priority. This
* is useful when the value from a lower source should
* preempt the one from higher ones.
*/
if (NM_DEVICE_GET_CLASS(self)->get_configured_mtu)
mtu = NM_DEVICE_GET_CLASS(self)->get_configured_mtu(self, &source, &force);
if (l3cd && !force && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG
&& (mtu2 = nm_l3_config_data_get_mtu(l3cd)) > 0) {
mtu = mtu2;
source = NM_DEVICE_MTU_SOURCE_IP_CONFIG;
}
if (mtu != 0) {
_LOGT(LOGD_DEVICE,
"mtu: value %u from source '%s' (%u), current source '%s' (%u)%s",
(guint) mtu,
nm_device_mtu_source_to_string(source),
(guint) source,
nm_device_mtu_source_to_string(priv->mtu_source),
(guint) priv->mtu_source,
force ? " (forced)" : "");
}
if (mtu != 0
&& (force || source > priv->mtu_source
|| (priv->mtu_source == NM_DEVICE_MTU_SOURCE_PARENT && source == priv->mtu_source)))
mtu_desired = mtu;
else {
mtu_desired = 0;
source = NM_DEVICE_MTU_SOURCE_NONE;
}
}
if (mtu_desired && mtu_desired < 1280) {
NMSettingIPConfig *s_ip6;
s_ip6 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP6_CONFIG);
if (s_ip6
&& !NM_IN_STRSET(nm_setting_ip_config_get_method(s_ip6),
NM_SETTING_IP6_CONFIG_METHOD_IGNORE,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
/* the interface has IPv6 enabled. The MTU with IPv6 cannot be smaller
* then 1280.
*
* For slave-devices (that don't have @s_ip6 we) don't do this fixup because
* it's anyway an unsolved problem when the slave configures a conflicting
* MTU. */
mtu_desired = 1280;
}
}
ip6_mtu = priv->ip6_mtu;
if (!ip6_mtu && priv->mtu_source == NM_DEVICE_MTU_SOURCE_NONE) {
/* initially, if the IPv6 MTU is not specified, grow it as large as the
* link MTU @mtu_desired. Only exception is, if @mtu_desired is so small
* to disable IPv6. */
if (mtu_desired >= 1280)
ip6_mtu = mtu_desired;
}
if (!ip6_mtu && !mtu_desired)
return;
mtu_desired_orig = mtu_desired;
ip6_mtu_orig = ip6_mtu;
mtu_plat = nm_platform_link_get_mtu(nm_device_get_platform(self), ifindex);
if (ip6_mtu) {
ip6_mtu = NM_MAX(1280, ip6_mtu);
if (!mtu_desired)
mtu_desired = mtu_plat;
if (mtu_desired) {
mtu_desired = NM_MAX(1280, mtu_desired);
if (mtu_desired < ip6_mtu)
ip6_mtu = mtu_desired;
}
}
#define _IP6_MTU_SYS() \
({ \
if (!ip6_mtu_sysctl.initialized) { \
ip6_mtu_sysctl.value = nm_device_sysctl_ip_conf_get_int_checked(self, \
AF_INET6, \
"mtu", \
10, \
0, \
G_MAXUINT32, \
0); \
ip6_mtu_sysctl.initialized = TRUE; \
} \
ip6_mtu_sysctl.value; \
})
if (mtu_desired && NM_DEVICE_GET_CLASS(self)->mtu_force_set && !priv->mtu_force_set_done) {
priv->mtu_force_set_done = TRUE;
if (mtu_desired == mtu_plat) {
if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
/* before touching any of the MTU parameters, record the
* original setting to restore on deactivation. */
priv->mtu_initial = mtu_plat;
priv->ip6_mtu_initial = _IP6_MTU_SYS();
}
mtu_plat--;
if (NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired - 1)) {
_LOGD(LOGD_DEVICE, "mtu: force-set MTU to %u", mtu_desired - 1);
} else
_LOGW(LOGD_DEVICE, "mtu: failure to force-set MTU to %u", mtu_desired - 1);
}
}
_LOGT(LOGD_DEVICE,
"mtu: device-mtu: %u%s, ipv6-mtu: %u%s, ifindex: %d",
(guint) mtu_desired,
mtu_desired == mtu_desired_orig
? ""
: nm_sprintf_buf(sbuf1, " (was %u)", (guint) mtu_desired_orig),
(guint) ip6_mtu,
ip6_mtu == ip6_mtu_orig ? "" : nm_sprintf_buf(sbuf2, " (was %u)", (guint) ip6_mtu_orig),
ifindex);
if ((mtu_desired && mtu_desired != mtu_plat) || (ip6_mtu && ip6_mtu != _IP6_MTU_SYS())) {
gboolean anticipated_failure = FALSE;
if (!priv->mtu_initial && !priv->ip6_mtu_initial) {
/* before touching any of the MTU parameters, record the
* original setting to restore on deactivation. */
priv->mtu_initial = mtu_plat;
priv->ip6_mtu_initial = _IP6_MTU_SYS();
}
if (mtu_desired && mtu_desired != mtu_plat) {
if (!NM_DEVICE_GET_CLASS(self)->set_platform_mtu(self, mtu_desired)) {
anticipated_failure = TRUE;
success = FALSE;
_LOGW(LOGD_DEVICE,
"mtu: failure to set MTU. %s",
NM_IS_DEVICE_VLAN(self)
? "Is the parent's MTU size large enough?"
: (!c_list_is_empty(&priv->slaves)
? "Are the MTU sizes of the slaves large enough?"
: "Did you configure the MTU correctly?"));
}
priv->carrier_wait_until_ms =
nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
}
if (ip6_mtu && ip6_mtu != _IP6_MTU_SYS()) {
if (!nm_device_sysctl_ip_conf_set(self,
AF_INET6,
"mtu",
nm_sprintf_buf(sbuf, "%u", (unsigned) ip6_mtu))) {
int errsv = errno;
NMLogLevel level = LOGL_WARN;
const char *msg = NULL;
success = FALSE;
if (anticipated_failure && errsv == EINVAL) {
level = LOGL_DEBUG;
msg = "Is the underlying MTU value successfully set?";
} else if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
level = LOGL_DEBUG;
msg = "IPv6 is disabled";
success = TRUE;
}
_NMLOG(level,
LOGD_DEVICE,
"mtu: failure to set IPv6 MTU%s%s",
msg ? ": " : "",
msg ?: "");
}
priv->carrier_wait_until_ms =
nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
}
}
if (success && source != NM_DEVICE_MTU_SOURCE_NONE)
priv->mtu_source = source;
#undef _IP6_MTU_SYS
}
void
nm_device_commit_mtu(NMDevice *self)
{
NMDeviceState state;
g_return_if_fail(NM_IS_DEVICE(self));
state = nm_device_get_state(self);
if (state >= NM_DEVICE_STATE_CONFIG && state < NM_DEVICE_STATE_DEACTIVATING) {
_LOGT(LOGD_DEVICE, "mtu: commit-mtu...");
_commit_mtu(self);
} else
_LOGT(LOGD_DEVICE,
"mtu: commit-mtu... skip due to state %s",
nm_device_state_to_string(state));
}
/*****************************************************************************/
static void
_dev_ipac6_ndisc_set_router_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const NML3ConfigData *l3cd;
if (!priv->ipac6_data.ndisc)
return;
if (nm_ndisc_get_node_type(priv->ipac6_data.ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
return;
/* FIXME(l3cfg): this doesn't seem right. What is the meaning of the l3cd at this
* point? Also, when do we need to reset the config (and call this function again?). */
l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, FALSE);
if (l3cd)
nm_ndisc_set_config(priv->ipac6_data.ndisc, l3cd);
}
static void
_dev_ipac6_set_state(NMDevice *self, NMDeviceIPState state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipac6_data.state != state) {
_LOGD_ipac6("set state: %s (was %s)",
nm_device_ip_state_to_string(state),
nm_device_ip_state_to_string(priv->ipac6_data.state));
priv->ipac6_data.state = state;
}
}
static void
_dev_ipac6_ndisc_config_changed(NMNDisc *ndisc,
const NMNDiscData *rdata,
guint changed_i,
const NML3ConfigData *l3cd,
NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean ready = TRUE;
NMDedupMultiIter iter;
const NMPObject *obj;
_dev_ipac6_grace_period_start(self, 0, TRUE);
_dev_l3_register_l3cds_set_one_full(self,
L3_CONFIG_DATA_TYPE_AC_6,
l3cd,
NM_L3CFG_CONFIG_FLAGS_FORCE_ONCE,
FALSE);
nm_clear_l3cd(&priv->ipac6_data.l3cd);
/* wait that addresses are committed to platform and
* become non-tentative before declaring AC6 is ready.*/
nm_l3_config_data_iter_obj_for_each (&iter, l3cd, &obj, NMP_OBJECT_TYPE_IP6_ADDRESS) {
const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
const NMPlatformIP6Address *plat_addr;
plat_addr = nm_platform_ip6_address_get(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
&addr->address);
if (!plat_addr || (plat_addr->n_ifa_flags & IFA_F_TENTATIVE)) {
ready = FALSE;
break;
}
}
if (ready) {
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
} else {
priv->ipac6_data.l3cd = nm_l3_config_data_ref(l3cd);
}
_dev_ipdhcp6_set_dhcp_level(self, rdata->dhcp_level);
_dev_l3_cfg_commit(self, FALSE);
_dev_ip_state_check_async(self, AF_INET6);
}
static void
_dev_ipac6_handle_timeout(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_LOGD_ipac6("timeout for autoconf (IPv6 router advertisement) reached");
nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET6);
}
static void
_dev_ipac6_ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self)
{
_dev_ipac6_handle_timeout(self);
}
static gboolean
_dev_ipac6_grace_period_expired(gpointer user_data)
{
_dev_ipac6_handle_timeout(user_data);
return G_SOURCE_REMOVE;
}
static gboolean
_dev_ipac6_grace_period_start(NMDevice *self, guint32 timeout_sec, gboolean force_restart)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean stopped;
/* In any other case (expired lease, assumed connection, etc.),
* wait for some time before failing the IP method.
*/
if (!force_restart && priv->ipac6_data.ndisc_grace_source) {
/* already pending. */
return FALSE;
}
/* Start a grace period equal to the RA timeout multiplied
* by a constant factor. */
stopped = nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
if (timeout_sec == 0) {
if (stopped)
_LOGD_ipac6("grace period stopped");
return FALSE;
}
nm_assert(timeout_sec <= G_MAXINT32);
if (timeout_sec >= G_MAXUINT / (GRACE_PERIOD_MULTIPLIER * 1000u))
timeout_sec = NM_RA_TIMEOUT_INFINITY;
if (timeout_sec == NM_RA_TIMEOUT_INFINITY) {
_LOGD_ipac6("grace period starts with infinity timeout");
priv->ipac6_data.ndisc_grace_source = g_source_ref(nm_g_source_sentinel_get(0));
} else {
_LOGD_ipac6("grace period starts with %u seconds", timeout_sec);
priv->ipac6_data.ndisc_grace_source =
nm_g_timeout_add_source(timeout_sec * (GRACE_PERIOD_MULTIPLIER * 1000u),
_dev_ipac6_grace_period_expired,
self);
}
return TRUE;
}
static void
_dev_ipac6_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
NMSettingIP6Config *s_ip = NULL;
NMNDiscNodeType node_type;
NMUtilsStableType stable_type;
const char *stable_id;
int max_addresses;
int router_solicitations;
int router_solicitation_interval;
guint32 ra_timeout;
guint32 default_ra_timeout;
NMUtilsIPv6IfaceId iid;
gboolean is_token;
if (priv->ipac6_data.state == NM_DEVICE_IP_STATE_NONE) {
if (!g_file_test("/proc/sys/net/ipv6", G_FILE_TEST_IS_DIR)) {
_LOGI_ipac6("addrconf6: kernel does not support IPv6");
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET6);
return;
}
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_PENDING);
}
if (NM_IN_SET(priv->ipll_data_6.state, NM_DEVICE_IP_STATE_NONE, NM_DEVICE_IP_STATE_PENDING)) {
_dev_ipac6_grace_period_start(self, 30, TRUE);
_dev_ipll6_start(self);
return;
}
if (priv->ipac6_data.ndisc) {
/* we already started. Nothing to do. */
return;
}
connection = nm_device_get_applied_connection(self);
if (connection)
s_ip = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
g_return_if_fail(s_ip);
if (nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET6),
NM_SETTING_IP6_CONFIG_METHOD_SHARED))
node_type = NM_NDISC_NODE_TYPE_ROUTER;
else
node_type = NM_NDISC_NODE_TYPE_HOST;
nm_ndisc_get_sysctl(nm_device_get_platform(self),
nm_device_get_ip_iface(self),
&max_addresses,
&router_solicitations,
&router_solicitation_interval,
&default_ra_timeout);
if (node_type == NM_NDISC_NODE_TYPE_ROUTER)
ra_timeout = 0u;
else {
ra_timeout = _prop_get_ipv6_ra_timeout(self);
if (ra_timeout == 0u)
ra_timeout = default_ra_timeout;
}
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
{
const NMNDiscConfig config = {
.l3cfg = nm_device_get_l3cfg(self),
.ifname = nm_device_get_ip_iface(self),
.stable_type = stable_type,
.network_id = stable_id,
.addr_gen_mode = nm_setting_ip6_config_get_addr_gen_mode(s_ip),
.node_type = node_type,
.max_addresses = max_addresses,
.router_solicitations = router_solicitations,
.router_solicitation_interval = router_solicitation_interval,
.ra_timeout = ra_timeout,
.ip6_privacy = _prop_get_ipv6_ip6_privacy(self),
};
priv->ipac6_data.ndisc = nm_lndp_ndisc_new(&config);
priv->ipac6_data.ndisc_changed_id =
g_signal_connect(priv->ipac6_data.ndisc,
NM_NDISC_CONFIG_RECEIVED,
G_CALLBACK(_dev_ipac6_ndisc_config_changed),
self);
priv->ipac6_data.ndisc_timeout_id =
g_signal_connect(priv->ipac6_data.ndisc,
NM_NDISC_RA_TIMEOUT_SIGNAL,
G_CALLBACK(_dev_ipac6_ndisc_ra_timeout),
self);
}
if (nm_device_get_ip_iface_identifier(self, &iid, FALSE, &is_token)) {
_LOGD_ipac6("using the device EUI-64 identifier");
nm_ndisc_set_iid(priv->ipac6_data.ndisc, iid, is_token);
} else {
/* Don't abort the addrconf at this point -- if ndisc needs the iid
* it will notice this itself. */
_LOGD_ipac6("no interface identifier; IPv6 address creation may fail");
}
if (nm_ndisc_get_node_type(priv->ipac6_data.ndisc) == NM_NDISC_NODE_TYPE_ROUTER) {
nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "1");
priv->needs_ip6_subnet = TRUE;
g_signal_emit(self, signals[IP6_SUBNET_NEEDED], 0);
}
_dev_ipac6_ndisc_set_router_config(self);
if (node_type == NM_NDISC_NODE_TYPE_ROUTER)
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_READY);
else
_dev_ipac6_grace_period_start(self, ra_timeout, TRUE);
nm_ndisc_start(priv->ipac6_data.ndisc);
}
void
nm_device_ip_method_autoconf6_start(NMDevice *self)
{
_dev_ipac6_start(self);
}
static void
_dev_ipac6_start_continue(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipac6_data.state != NM_DEVICE_IP_STATE_NONE)
_dev_ipac6_start(self);
}
static void
_dev_ipac6_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_g_source_inst(&priv->ipac6_data.ndisc_grace_source);
nm_clear_l3cd(&priv->ipac6_data.l3cd);
nm_clear_g_signal_handler(priv->ipac6_data.ndisc, &priv->ipac6_data.ndisc_changed_id);
nm_clear_g_signal_handler(priv->ipac6_data.ndisc, &priv->ipac6_data.ndisc_timeout_id);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_AC_6, NULL, FALSE);
if (priv->ipac6_data.ndisc) {
nm_ndisc_stop(priv->ipac6_data.ndisc);
g_clear_object(&priv->ipac6_data.ndisc);
}
_dev_ipac6_set_state(self, NM_DEVICE_IP_STATE_NONE);
}
/*****************************************************************************/
static void
_dev_sysctl_save_ip6_properties(NMDevice *self)
{
static const char *const ip6_properties_to_save[] = {
"accept_ra",
"forwarding",
"disable_ipv6",
"hop_limit",
"use_tempaddr",
};
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatform *platform = nm_device_get_platform(self);
const char *ifname;
char *value;
int i;
g_hash_table_remove_all(priv->ip6_saved_properties);
ifname = nm_device_get_ip_iface_from_platform(self);
if (!ifname)
return;
for (i = 0; i < G_N_ELEMENTS(ip6_properties_to_save); i++) {
value =
nm_platform_sysctl_ip_conf_get(platform, AF_INET6, ifname, ip6_properties_to_save[i]);
if (value) {
g_hash_table_insert(priv->ip6_saved_properties,
(char *) ip6_properties_to_save[i],
value);
}
}
}
static void
_dev_sysctl_restore_ip6_properties(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
GHashTableIter iter;
gpointer key;
gpointer value;
g_hash_table_iter_init(&iter, priv->ip6_saved_properties);
while (g_hash_table_iter_next(&iter, &key, &value))
nm_device_sysctl_ip_conf_set(self, AF_INET6, key, value);
}
static void
_dev_sysctl_set_disable_ipv6(NMDevice *self, gboolean do_disable)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
/* If we previously set addrgenmode=none, we are managing
* IPv6 in user space and we should not disable it. */
if (do_disable && priv->addrgenmode6_data.previous_mode_has
&& priv->addrgenmode6_data.previous_mode_val == NM_IN6_ADDR_GEN_MODE_NONE)
return;
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", do_disable ? "1" : "0");
}
/*****************************************************************************/
static void
_dev_addrgenmode6_set(NMDevice *self, guint8 addr_gen_mode)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int ifindex = nm_device_get_ip_ifindex(self);
const NMPlatformLink *plink;
int r;
int cur_addr_gen_mode;
char sbuf[100];
if (ifindex <= 0)
return;
plink = nm_platform_link_get(nm_device_get_platform(self), ifindex);
if (!plink)
return;
cur_addr_gen_mode = _nm_platform_link_get_inet6_addr_gen_mode(plink);
nm_assert(cur_addr_gen_mode >= 0 && cur_addr_gen_mode <= 255);
if (!priv->addrgenmode6_data.previous_mode_has) {
priv->addrgenmode6_data.previous_mode_has = TRUE;
priv->addrgenmode6_data.previous_mode_val = cur_addr_gen_mode;
nm_assert(priv->addrgenmode6_data.previous_mode_val == cur_addr_gen_mode);
}
_LOGD_ip(AF_INET6,
"addrgenmode6: set %s%s",
nm_platform_link_inet6_addrgenmode2str(addr_gen_mode, sbuf, sizeof(sbuf)),
(cur_addr_gen_mode == addr_gen_mode) ? " (already set)" : "");
if (cur_addr_gen_mode != addr_gen_mode) {
r = nm_platform_link_set_inet6_addr_gen_mode(nm_device_get_platform(self),
ifindex,
addr_gen_mode);
if (r < 0) {
_NMLOG_ip(NM_IN_SET(r, -NME_PL_NOT_FOUND, -NME_PL_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
AF_INET6,
"addrgenmode6: failed to set %s: (%s)",
nm_platform_link_inet6_addrgenmode2str(addr_gen_mode, sbuf, sizeof(sbuf)),
nm_strerror(r));
} else {
priv->addrgenmode6_data.previous_mode_val = addr_gen_mode;
}
}
if (addr_gen_mode == NM_IN6_ADDR_GEN_MODE_NONE) {
gs_free char *value = NULL;
/* Bounce IPv6 to ensure the kernel stops IPv6LL address and temporary
* address generation */
_LOGD_ip(AF_INET6,
"addrgenmode6: toggle disable_ipv6 sysctl after disabling addr-gen-mode");
value = nm_device_sysctl_ip_conf_get(self, AF_INET6, "disable_ipv6");
if (nm_streq0(value, "0")) {
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "0");
}
}
}
/*****************************************************************************/
static gboolean
ip_requires_slaves(NMDevice *self, int addr_family)
{
const char *method;
method = nm_device_get_effective_ip_config_method(self, addr_family);
if (NM_IS_IPv4(addr_family))
return nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO);
/* SLAAC, DHCP, and Link-Local depend on connectivity (and thus slaves)
* to complete addressing. SLAAC and DHCP need a peer to provide a prefix.
*/
return NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NM_SETTING_IP6_CONFIG_METHOD_DHCP);
}
static const char *
get_ip_method_auto(NMDevice *self, int addr_family)
{
return NM_IS_IPv4(addr_family) ? NM_SETTING_IP4_CONFIG_METHOD_AUTO
: NM_SETTING_IP6_CONFIG_METHOD_AUTO;
}
static void
activate_stage3_ip_config_for_addr_family(NMDevice *self, int addr_family, const char *method)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMConnection *connection;
int ip_ifindex;
if (nm_device_sys_iface_state_is_external(self))
goto out;
connection = nm_device_get_applied_connection(self);
g_return_if_fail(connection);
ip_ifindex = nm_device_get_ip_ifindex(self);
if (connection_ip_method_requires_carrier(connection, addr_family, NULL)
&& nm_device_is_master(self) && !priv->carrier) {
if (!priv->ip_data_x[IS_IPv4].wait_for_carrier) {
_LOGT_ip(addr_family, "waiting until carrier is on");
priv->ip_data_x[IS_IPv4].wait_for_carrier = TRUE;
}
goto out;
}
if (priv->ip_data_x[IS_IPv4].wait_for_carrier) {
_LOGT_ip(addr_family, "waiting until carrier completed");
priv->ip_data_x[IS_IPv4].wait_for_carrier = FALSE;
}
if (nm_device_is_master(self) && ip_requires_slaves(self, addr_family)) {
/* If the master has no ready slaves, and depends on slaves for
* a successful IP configuration attempt, then postpone IP addressing.
*/
if (!have_any_ready_slaves(self)) {
if (!priv->ip_data_x[IS_IPv4].wait_for_ports) {
_LOGT_ip(addr_family, "waiting for ports");
priv->ip_data_x[IS_IPv4].wait_for_ports = TRUE;
}
goto out;
}
}
if (priv->ip_data_x[IS_IPv4].wait_for_ports) {
_LOGT_ip(addr_family, "waiting until ports completed");
priv->ip_data_x[IS_IPv4].wait_for_ports = FALSE;
}
if (klass->ready_for_ip_config && !klass->ready_for_ip_config(self))
goto out_devip;
if (IS_IPv4) {
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO))
_dev_ipdhcpx_start(self, AF_INET);
else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL))
_dev_ipll4_start(self);
else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED))
_dev_ipshared4_start(self);
else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
/* pass */
} else
nm_assert_not_reached();
}
if (!IS_IPv4) {
if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
if (!priv->ip_data_x[IS_IPv4].is_disabled) {
priv->ip_data_x[IS_IPv4].is_disabled = TRUE;
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
}
} else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
if (!priv->ip_data_x[IS_IPv4].is_ignore) {
priv->ip_data_x[IS_IPv4].is_ignore = TRUE;
if (priv->master) {
/* If a device only has an IPv6 link-local address,
* we don't generate an assumed connection. Therefore,
* when a new slave connection (without IP configuration)
* is activated on the device, the link-local address
* remains configured. The IP configuration of an activated
* slave should not depend on the previous state. Flush
* addresses and routes on activation.
*/
if (ip_ifindex > 0) {
nm_platform_ip_route_flush(nm_device_get_platform(self),
AF_INET6,
ip_ifindex);
nm_platform_ip_address_flush(nm_device_get_platform(self),
AF_INET6,
ip_ifindex);
}
} else {
/* When activating an IPv6 'ignore' connection we need to revert back
* to kernel IPv6LL, but the kernel won't actually assign an address
* to the interface until disable_ipv6 is bounced.
*/
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
_dev_sysctl_set_disable_ipv6(self, TRUE);
_dev_sysctl_restore_ip6_properties(self);
}
}
} else {
_dev_ipll6_start(self);
if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO))
_dev_ipac6_start(self);
else if (NM_IN_STRSET(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED))
_dev_ipshared6_start(self);
else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
_dev_ipdhcpx_start(self, AF_INET6);
} else
nm_assert(NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_MANUAL,
NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL));
}
}
out_devip:
if (klass->act_stage3_ip_config)
klass->act_stage3_ip_config(self, addr_family);
out:
_dev_ip_state_check_async(self, addr_family);
}
static void
fw_change_zone_cb(NMFirewalldManager *firewalld_manager,
NMFirewalldManagerCallId *call_id,
GError *error,
gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->fw_call != call_id)
g_return_if_reached();
priv->fw_call = NULL;
if (nm_utils_error_is_cancelled(error))
return;
switch (priv->fw_state) {
case FIREWALL_STATE_WAIT_STAGE_3:
priv->fw_state = FIREWALL_STATE_INITIALIZED;
nm_device_activate_schedule_stage3_ip_config(self, TRUE);
break;
case FIREWALL_STATE_WAIT_IP_CONFIG:
priv->fw_state = FIREWALL_STATE_INITIALIZED;
if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
|| priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY)
nm_device_start_ip_check(self);
break;
case FIREWALL_STATE_INITIALIZED:
break;
default:
g_return_if_reached();
}
}
static void
fw_change_zone(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *applied_connection;
NMSettingConnection *s_con;
const char *zone;
nm_assert(priv->fw_state >= FIREWALL_STATE_INITIALIZED);
applied_connection = nm_device_get_applied_connection(self);
nm_assert(applied_connection);
s_con = nm_connection_get_setting_connection(applied_connection);
nm_assert(s_con);
if (priv->fw_call) {
nm_firewalld_manager_cancel_call(priv->fw_call);
nm_assert(!priv->fw_call);
}
if (G_UNLIKELY(!priv->fw_mgr))
priv->fw_mgr = g_object_ref(nm_firewalld_manager_get());
zone = nm_setting_connection_get_zone(s_con);
#if WITH_FIREWALLD_ZONE
if (!zone || zone[0] == '\0') {
if (nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET),
NM_SETTING_IP4_CONFIG_METHOD_SHARED)
|| nm_streq0(nm_device_get_effective_ip_config_method(self, AF_INET6),
NM_SETTING_IP6_CONFIG_METHOD_SHARED))
zone = "nm-shared";
}
#endif
priv->fw_call = nm_firewalld_manager_add_or_change_zone(priv->fw_mgr,
nm_device_get_ip_iface(self),
zone,
FALSE, /* change zone */
fw_change_zone_cb,
self);
}
static void
activate_stage3_ip_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
int ifindex;
const char *ipv4_method;
const char *ipv6_method;
/* stage3 is different from stage1+2.
*
* What is true in all cases is that when we start a stage, we call the corresponding
* nm_device_activate_schedule_stage*() function. But usually the stage cannot complete
* right away but needs to wait for some things to happen. So the activate_stage*() function
* returns, and will be later proceeded by calling *the same* stage again. That means,
* activate_stage*() must be re-entrant and be called repeatedly until we can proceed
* to the next stage. Only when the stage is completed, we schedule the next one.
*
* stage3 is different. It does IP configuration and as such (the stage handling itself)
* cannot fail. If a failure happens (for example for DHCP), we remember that (in priv->ipdhcp_data_x)
* and issue _dev_ip_state_check_async(). That one combines the DHCP state to determine the
* overall per-address-family state (priv->ip_data_x). Those states are then combined
* further into priv->combinedip_state, which then leads to nm_device_state_changed()
* (which for example can make the device fully ACTIVATED or FAILED).
*
* The difference between stage1+2 and stage3 is that IP configuration is running continuously
* while the device is active. As such the activate_stage3_ip_config() does not fail directly,
* unlike the other stages which can abort via NM_ACT_STAGE_RETURN_FAILURE. */
g_return_if_fail(priv->act_request.obj);
ifindex = nm_device_get_ip_ifindex(self);
if (priv->ip_data_4.do_reapply) {
_LOGD_ip(AF_INET, "reapply...");
_cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE, TRUE);
}
if (priv->ip_data_6.do_reapply) {
_LOGD_ip(AF_INET6, "reapply...");
_cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE, TRUE);
}
/* Add the interface to the specified firewall zone */
switch (priv->fw_state) {
case FIREWALL_STATE_UNMANAGED:
if (nm_device_sys_iface_state_is_external(self)) {
/* fake success */
priv->fw_state = FIREWALL_STATE_INITIALIZED;
} else if (ifindex > 0) {
priv->fw_state = FIREWALL_STATE_WAIT_STAGE_3;
fw_change_zone(self);
return;
}
/* no ifindex, nothing to do for now */
break;
case FIREWALL_STATE_WAIT_STAGE_3:
/* a firewall call for stage3 is pending. Return and wait. */
return;
default:
nm_assert(NM_IN_SET((FirewallState) priv->fw_state,
FIREWALL_STATE_INITIALIZED,
FIREWALL_STATE_WAIT_IP_CONFIG));
break;
}
nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
if (priv->state < NM_DEVICE_STATE_IP_CONFIG) {
_dev_ip_state_req_timeout_schedule(self, AF_INET);
_dev_ip_state_req_timeout_schedule(self, AF_INET6);
_active_connection_set_state_flags(self, NM_ACTIVATION_STATE_FLAG_LAYER2_READY);
nm_device_state_changed(self, NM_DEVICE_STATE_IP_CONFIG, NM_DEVICE_STATE_REASON_NONE);
/* Device should be up before we can do anything with it */
if (!nm_device_sys_iface_state_is_external(self) && ifindex > 0
&& !nm_platform_link_is_up(nm_device_get_platform(self), ifindex))
_LOGW(LOGD_DEVICE,
"interface %s not up for IP configuration",
nm_device_get_ip_iface(self));
}
ipv4_method = nm_device_get_effective_ip_config_method(self, AF_INET);
if (nm_streq(ipv4_method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
/* "auto" usually means DHCPv4 or autoconf6, but it doesn't have to be. Subclasses
* can overwrite it. For example, you cannot run DHCPv4 on PPP/WireGuard links. */
ipv4_method = klass->get_ip_method_auto(self, AF_INET);
}
ipv6_method = nm_device_get_effective_ip_config_method(self, AF_INET6);
if (nm_streq(ipv6_method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
ipv6_method = klass->get_ip_method_auto(self, AF_INET6);
}
if (!nm_device_sys_iface_state_is_external(self)
&& (!klass->ready_for_ip_config || klass->ready_for_ip_config(self))) {
if (priv->ipmanual_data.state_6 == NM_DEVICE_IP_STATE_NONE
&& !NM_IN_STRSET(ipv6_method,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
/* Ensure the MTU makes sense. If it was below 1280 the kernel would not
* expose any ipv6 sysctls or allow presence of any addresses on the interface,
* including LL, which * would make it impossible to autoconfigure MTU to a
* correct value. */
_commit_mtu(self);
/* Any method past this point requires an IPv6LL address. Use NM-controlled
* IPv6LL if this is not an assumed connection, since assumed connections
* will already have IPv6 set up.
*/
if (!nm_device_sys_iface_state_is_external_or_assume(self))
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
/* Re-enable IPv6 on the interface */
nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
_dev_sysctl_set_disable_ipv6(self, FALSE);
}
_dev_ipmanual_start(self);
}
activate_stage3_ip_config_for_addr_family(self, AF_INET, ipv4_method);
activate_stage3_ip_config_for_addr_family(self, AF_INET6, ipv6_method);
}
void
nm_device_activate_schedule_stage3_ip_config(NMDevice *self, gboolean do_sync)
{
activation_source_invoke_or_schedule(self, activate_stage3_ip_config, do_sync);
}
/*****************************************************************************/
static void
_dev_ipsharedx_set_state(NMDevice *self, int addr_family, NMDeviceIPState state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->ipshared_data_x[IS_IPv4].state != state) {
_LOGD_ipshared(addr_family,
"set state %s (was %s)",
nm_device_ip_state_to_string(state),
nm_device_ip_state_to_string(priv->ipshared_data_x[IS_IPv4].state));
priv->ipshared_data_x[IS_IPv4].state = state;
}
}
static void
_dev_ipsharedx_cleanup(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (IS_IPv4) {
if (priv->ipshared_data_4.v4.dnsmasq_manager) {
nm_clear_g_signal_handler(priv->ipshared_data_4.v4.dnsmasq_manager,
&priv->ipshared_data_4.v4.dnsmasq_state_id);
nm_dnsmasq_manager_stop(priv->ipshared_data_4.v4.dnsmasq_manager);
g_clear_object(&priv->ipshared_data_4.v4.dnsmasq_manager);
}
if (priv->ipshared_data_4.v4.firewall_config) {
nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, FALSE);
nm_clear_pointer(&priv->ipshared_data_4.v4.firewall_config, nm_firewall_config_free);
}
nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release);
nm_clear_l3cd(&priv->ipshared_data_4.v4.l3cd);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, NULL, FALSE);
}
_dev_ipsharedx_set_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
}
/*****************************************************************************/
static const NML3ConfigData *
_dev_ipshared4_new_l3cd(NMDevice *self, NMConnection *connection, NMPlatformIP4Address *out_addr4)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_auto_unref_l3cd_init NML3ConfigData *l3cd = NULL;
NMSettingIPConfig *s_ip4;
NMPlatformIP4Address address = {
.addr_source = NM_IP_CONFIG_SOURCE_SHARED,
};
g_return_val_if_fail(self, NULL);
g_return_val_if_fail(connection, NULL);
s_ip4 = nm_connection_get_setting_ip4_config(connection);
if (s_ip4 && nm_setting_ip_config_get_num_addresses(s_ip4) > 0) {
/* Use the first user-supplied address */
NMIPAddress *user = nm_setting_ip_config_get_address(s_ip4, 0);
in_addr_t a;
nm_ip_address_get_address_binary(user, &a);
nm_platform_ip4_address_set_addr(&address, a, nm_ip_address_get_prefix(user));
nm_clear_pointer(&priv->ipshared_data_4.v4.shared_ip_handle, nm_netns_shared_ip_release);
} else {
if (!priv->ipshared_data_4.v4.shared_ip_handle)
priv->ipshared_data_4.v4.shared_ip_handle =
nm_netns_shared_ip_reserve(nm_device_get_netns(self));
nm_platform_ip4_address_set_addr(&address,
priv->ipshared_data_4.v4.shared_ip_handle->addr,
24);
}
l3cd = nm_device_create_l3_config_data(self, NM_IP_CONFIG_SOURCE_SHARED);
nm_l3_config_data_add_address_4(l3cd, &address);
NM_SET_OUT(out_addr4, address);
return nm_l3_config_data_seal(g_steal_pointer(&l3cd));
}
static gboolean
_dev_ipshared4_init(NMDevice *self)
{
static const char *const modules[] = {"ip_tables",
"iptable_nat",
"nf_nat_ftp",
"nf_nat_irc",
"nf_nat_sip",
"nf_nat_tftp",
"nf_nat_pptp",
"nf_nat_h323"};
int errsv;
guint i;
if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
-1)
== 1) {
/* nothing to do. */
} else if (!nm_platform_sysctl_set(nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_forward"),
"1")) {
errsv = errno;
_LOGW_ipshared(AF_INET, "error enabling IPv4 forwarding: %s", nm_strerror_native(errsv));
return FALSE;
}
if (nm_platform_sysctl_get_int32(nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"),
-1)
== 1) {
/* nothing to do. */
} else if (!nm_platform_sysctl_set(nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv4/ip_dynaddr"),
"1")) {
errsv = errno;
_LOGD_ipshared(AF_INET,
"share: error enabling dynamic addresses: %s",
nm_strerror_native(errsv));
}
for (i = 0; i < G_N_ELEMENTS(modules); i++)
nmp_utils_modprobe(NULL, FALSE, modules[i], NULL);
return TRUE;
}
static void
_dev_ipshared4_dnsmasq_state_changed_cb(NMDnsMasqManager *manager, guint status, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
if (status != NM_DNSMASQ_STATUS_DEAD)
return;
_dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET);
}
static void
_dev_ipshared4_start(NMDevice *self)
{
nm_auto_unref_l3cd const NML3ConfigData *l3cd = NULL;
NMPlatformIP4Address ip4_addr;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *ip_iface;
NMConnection *applied;
if (priv->ipshared_data_4.state != NM_DEVICE_IP_STATE_NONE)
return;
nm_assert(!priv->ipshared_data_4.v4.firewall_config);
nm_assert(!priv->ipshared_data_4.v4.dnsmasq_manager);
nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
ip_iface = nm_device_get_ip_iface(self);
g_return_if_fail(ip_iface);
applied = nm_device_get_applied_connection(self);
g_return_if_fail(applied);
_dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_PENDING);
l3cd = _dev_ipshared4_new_l3cd(self, applied, &ip4_addr);
if (!l3cd) {
nm_assert_not_reached();
goto out_fail;
}
if (!_dev_ipshared4_init(self))
goto out_fail;
priv->ipshared_data_4.v4.firewall_config =
nm_firewall_config_new(ip_iface, ip4_addr.address, ip4_addr.plen);
nm_firewall_config_apply(priv->ipshared_data_4.v4.firewall_config, TRUE);
priv->ipshared_data_4.v4.l3cd = nm_l3_config_data_ref(l3cd);
_dev_l3_register_l3cds_set_one(self, L3_CONFIG_DATA_TYPE_SHARED_4, l3cd, FALSE);
/* Wait that the address gets committed before spawning dnsmasq */
return;
out_fail:
_dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET);
}
static void
_dev_ipshared4_spawn_dnsmasq(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const char *ip_iface;
gs_free_error GError *error = NULL;
NMSettingConnection *s_con;
gboolean announce_android_metered;
NMConnection *applied;
nm_assert(priv->ipshared_data_4.v4.firewall_config);
nm_assert(priv->ipshared_data_4.v4.dnsmasq_state_id == 0);
nm_assert(!priv->ipshared_data_4.v4.dnsmasq_manager);
nm_assert(priv->ipshared_data_4.v4.l3cd);
ip_iface = nm_device_get_ip_iface(self);
g_return_if_fail(ip_iface);
applied = nm_device_get_applied_connection(self);
g_return_if_fail(applied);
s_con = nm_connection_get_setting_connection(applied);
switch (nm_setting_connection_get_metered(s_con)) {
case NM_METERED_YES:
/* honor the metered flag. Note that reapply on the device does not affect
* the metered setting. This is different from other profiles, where the
* metered flag of an activated profile can be changed (reapplied). */
announce_android_metered = TRUE;
break;
case NM_METERED_UNKNOWN:
/* we pick up the current value and announce it. But again, we cannot update
* the announced setting without restarting dnsmasq. That means, if the default
* route changes w.r.t. being metered, then the shared connection does not get
* updated before reactivating. */
announce_android_metered =
NM_IN_SET(nm_manager_get_metered(NM_MANAGER_GET), NM_METERED_YES, NM_METERED_GUESS_YES);
break;
default:
announce_android_metered = FALSE;
break;
}
priv->ipshared_data_4.v4.dnsmasq_manager = nm_dnsmasq_manager_new(ip_iface);
if (!nm_dnsmasq_manager_start(priv->ipshared_data_4.v4.dnsmasq_manager,
priv->ipshared_data_4.v4.l3cd,
announce_android_metered,
&error)) {
_LOGW_ipshared(AF_INET, "could not start dnsmasq: %s", error->message);
goto out_fail;
}
priv->ipshared_data_4.v4.dnsmasq_state_id =
g_signal_connect(priv->ipshared_data_4.v4.dnsmasq_manager,
NM_DNS_MASQ_MANAGER_STATE_CHANGED,
G_CALLBACK(_dev_ipshared4_dnsmasq_state_changed_cb),
self);
_dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_INET);
return;
out_fail:
_dev_ipsharedx_set_state(self, AF_INET, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET);
}
/*****************************************************************************/
static void
_dev_ipshared6_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_dev_ipac6_start(self);
if (priv->ipshared_data_6.state != NM_DEVICE_IP_STATE_NONE)
return;
if (!nm_platform_sysctl_set(
nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/all/forwarding"),
"1")) {
_LOGW_ipshared(AF_INET6, "failure to enable ipv6 forwarding");
_dev_ipsharedx_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_FAILED);
_dev_ip_state_check_async(self, AF_INET6);
return;
}
_dev_ipsharedx_set_state(self, AF_INET6, NM_DEVICE_IP_STATE_READY);
_dev_ip_state_check_async(self, AF_INET6);
}
/*****************************************************************************/
static void
act_request_set(NMDevice *self, NMActRequest *act_request)
{
NMDevicePrivate *priv;
nm_assert(NM_IS_DEVICE(self));
nm_assert(!act_request || NM_IS_ACT_REQUEST(act_request));
priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->act_request.visible && priv->act_request.obj == act_request)
return;
/* always clear the public flag. The few callers that set a new @act_request
* don't want that the property is public yet. */
nm_dbus_track_obj_path_set(&priv->act_request, act_request, FALSE);
if (act_request) {
switch (nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(act_request))) {
case NM_ACTIVATION_TYPE_EXTERNAL:
break;
case NM_ACTIVATION_TYPE_ASSUME:
if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_EXTERNAL)
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_ASSUME);
break;
case NM_ACTIVATION_TYPE_MANAGED:
if (NM_IN_SET_TYPED(NMDeviceSysIfaceState,
priv->sys_iface_state,
NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
NM_DEVICE_SYS_IFACE_STATE_ASSUME))
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
break;
}
}
}
gboolean
nm_device_is_nm_owned(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->nm_owned;
}
/*
* delete_on_deactivate_link_delete
*
* Function will be queued with g_idle_add to call
* nm_platform_link_delete for the underlying resources
* of the device.
*/
static gboolean
delete_on_deactivate_link_delete(gpointer user_data)
{
DeleteOnDeactivateData *data = user_data;
nm_auto_unref_object NMDevice *self = data->device;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free_error GError *error = NULL;
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: cleanup and delete virtual link (id=%u)",
data->idle_add_id);
priv->delete_on_deactivate_data = NULL;
if (!nm_device_unrealize(self, TRUE, &error))
_LOGD(LOGD_DEVICE, "delete_on_deactivate: unrealizing failed (%s)", error->message);
nm_device_emit_recheck_auto_activate(self);
g_free(data);
return FALSE;
}
static void
delete_on_deactivate_unschedule(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->delete_on_deactivate_data) {
DeleteOnDeactivateData *data = priv->delete_on_deactivate_data;
priv->delete_on_deactivate_data = NULL;
g_source_remove(data->idle_add_id);
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: cancel cleanup and delete virtual link (id=%u)",
data->idle_add_id);
g_object_unref(data->device);
g_free(data);
}
}
static void
delete_on_deactivate_check_and_schedule(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
DeleteOnDeactivateData *data;
if (!priv->nm_owned)
return;
if (priv->queued_act_request)
return;
if (!nm_device_is_software(self) || !nm_device_is_real(self))
return;
if (nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED)
return;
if (nm_device_get_state(self) == NM_DEVICE_STATE_UNAVAILABLE)
return;
delete_on_deactivate_unschedule(self); /* always cancel and reschedule */
data = g_new(DeleteOnDeactivateData, 1);
data->device = g_object_ref(self);
data->idle_add_id = g_idle_add(delete_on_deactivate_link_delete, data);
priv->delete_on_deactivate_data = data;
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: schedule cleanup and delete virtual link (id=%u)",
data->idle_add_id);
}
static void
_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type, gboolean from_reapply)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
_dev_ipsharedx_cleanup(self, addr_family);
_dev_ipdev_cleanup(self, AF_UNSPEC);
_dev_ipdev_cleanup(self, addr_family);
_dev_ipdhcpx_cleanup(self, addr_family, TRUE, FALSE);
if (!IS_IPv4)
_dev_ipac6_cleanup(self);
_dev_ipllx_cleanup(self, addr_family);
_dev_ipmanual_cleanup(self);
_dev_ip_state_cleanup(self, AF_UNSPEC, from_reapply);
_dev_ip_state_cleanup(self, addr_family, from_reapply);
}
gboolean
_nm_device_hash_check_invalid_keys(GHashTable *hash,
const char *setting_name,
GError **error,
const char *const *whitelist)
{
guint found_whitelisted_keys = 0;
guint i;
nm_assert(hash && g_hash_table_size(hash) > 0);
nm_assert(whitelist && whitelist[0]);
#if NM_MORE_ASSERTS > 10
/* Require whitelist to only contain unique keys. */
{
gs_unref_hashtable GHashTable *check_dups =
g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, NULL);
for (i = 0; whitelist[i]; i++) {
if (!g_hash_table_add(check_dups, (char *) whitelist[i]))
nm_assert(FALSE);
}
nm_assert(g_hash_table_size(check_dups) > 0);
}
#endif
for (i = 0; whitelist[i]; i++) {
if (g_hash_table_contains(hash, whitelist[i]))
found_whitelisted_keys++;
}
if (found_whitelisted_keys == g_hash_table_size(hash)) {
/* Good, there are only whitelisted keys in the hash. */
return TRUE;
}
if (error) {
GHashTableIter iter;
const char *k = NULL;
const char *first_invalid_key = NULL;
g_hash_table_iter_init(&iter, hash);
while (g_hash_table_iter_next(&iter, (gpointer *) &k, NULL)) {
if (nm_strv_find_first(whitelist, -1, k) < 0) {
first_invalid_key = k;
break;
}
}
if (setting_name) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
"Can't reapply changes to '%s.%s' setting",
setting_name,
first_invalid_key);
} else {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
"Can't reapply any changes to '%s' setting",
first_invalid_key);
}
g_return_val_if_fail(first_invalid_key, FALSE);
}
return FALSE;
}
static void
_pacrunner_manager_add(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id);
priv->pacrunner_conf_id = nm_pacrunner_manager_add(nm_pacrunner_manager_get(),
nm_device_get_ip_iface(self),
nm_device_get_l3cd(self, TRUE));
}
static void
reactivate_proxy_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->pacrunner_conf_id)
_pacrunner_manager_add(self);
}
/*****************************************************************************/
static gboolean
can_reapply_change(NMDevice *self,
const char *setting_name,
NMSetting *s_old,
NMSetting *s_new,
GHashTable *diffs,
GError **error)
{
if (nm_streq(setting_name, NM_SETTING_CONNECTION_SETTING_NAME)) {
/* Whitelist allowed properties from "connection" setting which are
* allowed to differ.
*
* This includes UUID, there is no principal problem with reapplying a
* connection and changing its UUID. In fact, disallowing it makes it
* cumbersome for the user to reapply any connection but the original
* settings-connection. */
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_CONNECTION_SETTING_NAME,
error,
NM_SETTING_CONNECTION_ID,
NM_SETTING_CONNECTION_UUID,
NM_SETTING_CONNECTION_STABLE_ID,
NM_SETTING_CONNECTION_AUTOCONNECT,
NM_SETTING_CONNECTION_ZONE,
NM_SETTING_CONNECTION_METERED,
NM_SETTING_CONNECTION_LLDP,
NM_SETTING_CONNECTION_MDNS,
NM_SETTING_CONNECTION_LLMNR,
NM_SETTING_CONNECTION_DNS_OVER_TLS);
}
if (NM_IN_STRSET(setting_name,
NM_SETTING_USER_SETTING_NAME,
NM_SETTING_PROXY_SETTING_NAME,
NM_SETTING_IP4_CONFIG_SETTING_NAME,
NM_SETTING_IP6_CONFIG_SETTING_NAME))
return TRUE;
if (nm_streq(setting_name, NM_SETTING_WIRED_SETTING_NAME)) {
if (NM_IN_SET(NM_DEVICE_GET_CLASS(self)->get_configured_mtu,
nm_device_get_configured_mtu_wired_parent,
nm_device_get_configured_mtu_for_wired)) {
return nm_device_hash_check_invalid_keys(diffs,
NM_SETTING_WIRED_SETTING_NAME,
error,
NM_SETTING_WIRED_MTU);
}
goto out_fail;
}
if (nm_streq(setting_name, NM_SETTING_OVS_EXTERNAL_IDS_SETTING_NAME)
&& NM_DEVICE_GET_CLASS(self)->can_reapply_change_ovs_external_ids) {
/* TODO: this means, you cannot reapply changes to the external-ids for
* OVS system interfaces. */
return TRUE;
}
out_fail:
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_INCOMPATIBLE_CONNECTION,
"Can't reapply any changes to '%s' setting",
setting_name);
return FALSE;
}
static void
reapply_connection(NMDevice *self, NMConnection *con_old, NMConnection *con_new)
{}
/* check_and_reapply_connection:
* @connection: the new connection settings to be applied or %NULL to reapply
* the current settings connection
* @version_id: either zero, or the current version id for the applied
* connection.
* @audit_args: on return, a string representing the changes
* @error: the error if %FALSE is returned
*
* Change configuration of an already configured device if possible.
* Updates the device's applied connection upon success.
*
* Return: %FALSE if the new configuration can not be reapplied.
*/
static gboolean
check_and_reapply_connection(NMDevice *self,
NMConnection *connection,
guint64 version_id,
char **audit_args,
GError **error)
{
NMDeviceClass *klass = NM_DEVICE_GET_CLASS(self);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *applied = nm_device_get_applied_connection(self);
gs_unref_object NMConnection *applied_clone = NULL;
gs_unref_hashtable GHashTable *diffs = NULL;
NMConnection *con_old;
NMConnection *con_new;
GHashTableIter iter;
if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
"Device is not activated");
return FALSE;
}
nm_connection_diff(connection,
applied,
NM_SETTING_COMPARE_FLAG_IGNORE_TIMESTAMP
| NM_SETTING_COMPARE_FLAG_IGNORE_SECRETS,
&diffs);
if (audit_args) {
if (diffs && nm_audit_manager_audit_enabled(nm_audit_manager_get()))
*audit_args = nm_utils_format_con_diff_for_audit(diffs);
else
*audit_args = NULL;
}
/**************************************************************************
* check for unsupported changes and reject to reapply
*************************************************************************/
if (diffs) {
char *setting_name;
GHashTable *setting_diff;
g_hash_table_iter_init(&iter, diffs);
while (
g_hash_table_iter_next(&iter, (gpointer *) &setting_name, (gpointer *) &setting_diff)) {
if (!klass->can_reapply_change(
self,
setting_name,
nm_connection_get_setting_by_name(applied, setting_name),
nm_connection_get_setting_by_name(connection, setting_name),
setting_diff,
error))
return FALSE;
}
}
if (version_id != 0
&& version_id
!= nm_active_connection_version_id_get(
(NMActiveConnection *) priv->act_request.obj)) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_VERSION_ID_MISMATCH,
"Reapply failed because device changed in the meantime and the "
"version-id mismatches");
return FALSE;
}
/**************************************************************************
* Update applied connection
*************************************************************************/
if (diffs)
nm_active_connection_version_id_bump((NMActiveConnection *) priv->act_request.obj);
_LOGD(LOGD_DEVICE,
"reapply (version-id %llu%s)",
(unsigned long long) nm_active_connection_version_id_get(
((NMActiveConnection *) priv->act_request.obj)),
diffs ? "" : " (unmodified)");
if (diffs) {
NMConnection *connection_clean = connection;
gs_unref_object NMConnection *connection_clean_free = NULL;
{
NMSettingConnection *s_con_a, *s_con_n;
/* we allow re-applying a connection with differing ID, UUID, STABLE_ID and AUTOCONNECT.
* This is for convenience but these values are not actually changeable. So, check
* if they changed, and if the did revert to the original values. */
s_con_a = nm_connection_get_setting_connection(applied);
s_con_n = nm_connection_get_setting_connection(connection);
if (!nm_streq(nm_setting_connection_get_id(s_con_a),
nm_setting_connection_get_id(s_con_n))
|| !nm_streq(nm_setting_connection_get_uuid(s_con_a),
nm_setting_connection_get_uuid(s_con_n))
|| nm_setting_connection_get_autoconnect(s_con_a)
!= nm_setting_connection_get_autoconnect(s_con_n)
|| !nm_streq0(nm_setting_connection_get_stable_id(s_con_a),
nm_setting_connection_get_stable_id(s_con_n))) {
connection_clean_free = nm_simple_connection_new_clone(connection);
connection_clean = connection_clean_free;
s_con_n = nm_connection_get_setting_connection(connection_clean);
g_object_set(s_con_n,
NM_SETTING_CONNECTION_ID,
nm_setting_connection_get_id(s_con_a),
NM_SETTING_CONNECTION_UUID,
nm_setting_connection_get_uuid(s_con_a),
NM_SETTING_CONNECTION_AUTOCONNECT,
nm_setting_connection_get_autoconnect(s_con_a),
NM_SETTING_CONNECTION_STABLE_ID,
nm_setting_connection_get_stable_id(s_con_a),
NULL);
}
}
con_old = applied_clone = nm_simple_connection_new_clone(applied);
con_new = applied;
/* FIXME(applied-connection-immutable): we should not modify the applied
* connection but replace it with a new (immutable) instance. */
nm_connection_replace_settings_from_connection(applied, connection_clean);
nm_connection_clear_secrets(applied);
} else
con_old = con_new = applied;
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = FALSE;
priv->l3config_merge_flags_has = FALSE;
/**************************************************************************
* Reapply changes
*
* Note that reapply_connection() is called as very first. This is for example
* important for NMDeviceWireGuard, which implements coerce_route_table()
* and get_extra_rules().
* That is because NMDeviceWireGuard caches settings, so during reapply that
* cache must be updated *first*.
*************************************************************************/
klass->reapply_connection(self, con_old, con_new);
if (priv->state >= NM_DEVICE_STATE_CONFIG)
lldp_setup(self, NM_TERNARY_DEFAULT);
if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
/* Allow reapply of MTU */
priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
if (nm_g_hash_table_lookup(diffs, NM_SETTING_IP4_CONFIG_SETTING_NAME))
priv->ip_data_4.do_reapply = TRUE;
if (nm_g_hash_table_lookup(diffs, NM_SETTING_IP6_CONFIG_SETTING_NAME))
priv->ip_data_6.do_reapply = TRUE;
nm_device_activate_schedule_stage3_ip_config(self, FALSE);
_routing_rules_sync(self, NM_TERNARY_TRUE);
reactivate_proxy_config(self);
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, FALSE);
}
if (priv->state >= NM_DEVICE_STATE_IP_CHECK)
nm_device_update_firewall_zone(self);
if (priv->state >= NM_DEVICE_STATE_ACTIVATED)
nm_device_update_metered(self);
return TRUE;
}
gboolean
nm_device_reapply(NMDevice *self, NMConnection *connection, GError **error)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return check_and_reapply_connection(self, connection, 0, NULL, error);
}
typedef struct {
NMConnection *connection;
guint64 version_id;
} ReapplyData;
static void
reapply_cb(NMDevice *self,
GDBusMethodInvocation *context,
NMAuthSubject *subject,
GError *error,
gpointer user_data)
{
ReapplyData *reapply_data = user_data;
guint64 version_id = 0;
gs_unref_object NMConnection *connection = NULL;
GError *local = NULL;
gs_free char *audit_args = NULL;
if (reapply_data) {
connection = reapply_data->connection;
version_id = reapply_data->version_id;
g_slice_free(ReapplyData, reapply_data);
}
if (error) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
self,
FALSE,
NULL,
subject,
error->message);
g_dbus_method_invocation_return_gerror(context, error);
return;
}
if (nm_device_sys_iface_state_is_external(self))
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
if (!check_and_reapply_connection(self,
connection
?: nm_device_get_settings_connection_get_connection(self),
version_id,
&audit_args,
&local)) {
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
self,
FALSE,
audit_args,
subject,
local->message);
g_dbus_method_invocation_take_error(context, g_steal_pointer(&local));
return;
}
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY, self, TRUE, audit_args, subject, NULL);
g_dbus_method_invocation_return_value(context, NULL);
}
static void
impl_device_reapply(NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *dbus_connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMDevice *self = NM_DEVICE(obj);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingsConnection *settings_connection;
NMConnection *connection = NULL;
GError *error = NULL;
ReapplyData *reapply_data;
gs_unref_variant GVariant *settings = NULL;
guint64 version_id;
guint32 flags;
g_variant_get(parameters, "(@a{sa{sv}}tu)", &settings, &version_id, &flags);
/* No flags supported as of now. */
if (flags != 0) {
error =
g_error_new_literal(NM_DEVICE_ERROR, NM_DEVICE_ERROR_FAILED, "Invalid flags specified");
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
self,
FALSE,
NULL,
invocation,
error->message);
g_dbus_method_invocation_take_error(invocation, error);
return;
}
if (priv->state < NM_DEVICE_STATE_PREPARE || priv->state > NM_DEVICE_STATE_ACTIVATED) {
error = g_error_new_literal(NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
"Device is not activated");
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
self,
FALSE,
NULL,
invocation,
error->message);
g_dbus_method_invocation_take_error(invocation, error);
return;
}
settings_connection = nm_device_get_settings_connection(self);
g_return_if_fail(settings_connection);
if (settings && g_variant_n_children(settings)) {
/* New settings specified inline. */
connection = _nm_simple_connection_new_from_dbus(settings,
NM_SETTING_PARSE_FLAGS_STRICT
| NM_SETTING_PARSE_FLAGS_NORMALIZE,
&error);
if (!connection) {
g_prefix_error(&error, "The settings specified are invalid: ");
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_REAPPLY,
self,
FALSE,
NULL,
invocation,
error->message);
g_dbus_method_invocation_take_error(invocation, error);
return;
}
nm_connection_clear_secrets(connection);
}
if (connection || version_id) {
reapply_data = g_slice_new(ReapplyData);
reapply_data->connection = connection;
reapply_data->version_id = version_id;
} else
reapply_data = NULL;
nm_device_auth_request(self,
invocation,
nm_device_get_applied_connection(self),
NM_AUTH_PERMISSION_NETWORK_CONTROL,
TRUE,
NULL,
reapply_cb,
reapply_data);
}
/*****************************************************************************/
static void
impl_device_get_applied_connection(NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMDevice *self = NM_DEVICE(obj);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free_error GError *error = NULL;
NMConnection *applied_connection;
guint32 flags;
GVariant *var_settings;
g_variant_get(parameters, "(u)", &flags);
/* No flags supported as of now. */
if (flags != 0) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"Invalid flags specified");
return;
}
applied_connection = nm_device_get_applied_connection(self);
if (!applied_connection) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
"Device is not activated");
return;
}
if (!nm_auth_is_invocation_in_acl_set_error(applied_connection,
invocation,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_PERMISSION_DENIED,
NULL,
&error)) {
g_dbus_method_invocation_take_error(invocation, g_steal_pointer(&error));
return;
}
var_settings =
nm_connection_to_dbus(applied_connection, NM_CONNECTION_SERIALIZE_WITH_NON_SECRET);
if (!var_settings)
var_settings = nm_g_variant_singleton_aLsaLsvII();
g_dbus_method_invocation_return_value(
invocation,
g_variant_new(
"(@a{sa{sv}}t)",
var_settings,
nm_active_connection_version_id_get((NMActiveConnection *) priv->act_request.obj)));
}
/*****************************************************************************/
static void
disconnect_cb(NMDevice *self,
GDBusMethodInvocation *context,
NMAuthSubject *subject,
GError *error,
gpointer user_data)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
GError *local = NULL;
if (error) {
g_dbus_method_invocation_return_gerror(context, error);
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT,
self,
FALSE,
NULL,
subject,
error->message);
return;
}
/* Authorized */
if (priv->state <= NM_DEVICE_STATE_DISCONNECTED) {
local = g_error_new_literal(NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
"Device is not active");
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT,
self,
FALSE,
NULL,
subject,
local->message);
g_dbus_method_invocation_take_error(context, local);
} else {
nm_device_autoconnect_blocked_set(self, NM_DEVICE_AUTOCONNECT_BLOCKED_MANUAL_DISCONNECT);
nm_device_state_changed(self,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
g_dbus_method_invocation_return_value(context, NULL);
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DISCONNECT, self, TRUE, NULL, subject, NULL);
}
}
static void
_clear_queued_act_request(NMDevicePrivate *priv, NMActiveConnectionStateReason active_reason)
{
if (priv->queued_act_request) {
gs_unref_object NMActRequest *ac = NULL;
ac = g_steal_pointer(&priv->queued_act_request);
nm_active_connection_set_state_fail((NMActiveConnection *) ac, active_reason, NULL);
}
}
static void
impl_device_disconnect(NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *dbus_connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMDevice *self = NM_DEVICE(obj);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
if (!priv->act_request.obj) {
g_dbus_method_invocation_return_error_literal(invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_ACTIVE,
"This device is not active");
return;
}
connection = nm_device_get_applied_connection(self);
nm_assert(connection);
nm_device_auth_request(self,
invocation,
connection,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
TRUE,
NULL,
disconnect_cb,
NULL);
}
static void
delete_cb(NMDevice *self,
GDBusMethodInvocation *context,
NMAuthSubject *subject,
GError *error,
gpointer user_data)
{
GError *local = NULL;
if (error) {
g_dbus_method_invocation_return_gerror(context, error);
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE,
self,
FALSE,
NULL,
subject,
error->message);
return;
}
/* Authorized */
nm_audit_log_device_op(NM_AUDIT_OP_DEVICE_DELETE, self, TRUE, NULL, subject, NULL);
if (nm_device_unrealize(self, TRUE, &local))
g_dbus_method_invocation_return_value(context, NULL);
else
g_dbus_method_invocation_take_error(context, local);
}
static void
impl_device_delete(NMDBusObject *obj,
const NMDBusInterfaceInfoExtended *interface_info,
const NMDBusMethodInfoExtended *method_info,
GDBusConnection *connection,
const char *sender,
GDBusMethodInvocation *invocation,
GVariant *parameters)
{
NMDevice *self = NM_DEVICE(obj);
if (!nm_device_is_software(self) || !nm_device_is_real(self)) {
g_dbus_method_invocation_return_error_literal(
invocation,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_NOT_SOFTWARE,
"This device is not a software device or is not realized");
return;
}
nm_device_auth_request(self,
invocation,
NULL,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
TRUE,
NULL,
delete_cb,
NULL);
}
static void
_device_activate(NMDevice *self, NMActRequest *req)
{
NMConnection *connection;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
nm_assert(nm_device_is_real(self));
/* Ensure the activation request is still valid; the master may have
* already failed in which case activation of this device should not proceed.
*/
if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req))
>= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING)
return;
if (!nm_device_get_managed(self, FALSE)) {
/* It's unclear why the device would be unmanaged at this point.
* Just to be sure, handle it and error out. */
_LOGE(LOGD_DEVICE,
"Activation: failed activating connection '%s' because device is still unmanaged",
nm_active_connection_get_settings_connection_id((NMActiveConnection *) req));
nm_active_connection_set_state_fail((NMActiveConnection *) req,
NM_ACTIVE_CONNECTION_STATE_REASON_UNKNOWN,
NULL);
return;
}
connection = nm_act_request_get_applied_connection(req);
nm_assert(connection);
_LOGI(LOGD_DEVICE,
"Activation: starting connection '%s' (%s)",
nm_connection_get_id(connection),
nm_connection_get_uuid(connection));
delete_on_deactivate_unschedule(self);
act_request_set(self, req);
nm_device_activate_schedule_stage1_device_prepare(self, FALSE);
}
static void
_carrier_wait_check_queued_act_request(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->queued_act_request || !priv->queued_act_request_is_waiting_for_carrier)
return;
priv->queued_act_request_is_waiting_for_carrier = FALSE;
if (!priv->carrier) {
_LOGD(LOGD_DEVICE, "Cancel queued activation request as we have no carrier after timeout");
_clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
} else if (priv->state == NM_DEVICE_STATE_DISCONNECTED) {
gs_unref_object NMActRequest *queued_req = NULL;
_LOGD(LOGD_DEVICE, "Activate queued activation request as we now have carrier");
queued_req = g_steal_pointer(&priv->queued_act_request);
_device_activate(self, queued_req);
}
}
static gboolean
_carrier_wait_check_act_request_must_queue(NMDevice *self, NMActRequest *req)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
/* If we have carrier or if we are not waiting for it, the activation
* request is not blocked waiting for carrier. */
if (priv->carrier)
return FALSE;
if (priv->carrier_wait_id == 0)
return FALSE;
connection = nm_act_request_get_applied_connection(req);
if (!connection_requires_carrier(connection))
return FALSE;
if (!nm_device_check_connection_available(self,
connection,
NM_DEVICE_CHECK_CON_AVAILABLE_ALL,
NULL,
NULL)) {
/* We passed all @flags we have, and no @specific_object.
* This equals maximal availability, if a connection is not available
* in this case, it is not waiting for carrier.
*
* Actually, why are we even trying to activate it? Strange, but whatever
* the reason, don't wait for carrier.
*/
return FALSE;
}
if (nm_device_check_connection_available(
self,
connection,
NM_DEVICE_CHECK_CON_AVAILABLE_ALL
& ~_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER,
NULL,
NULL)) {
/* The connection was available with flags ALL, and it is still available
* if we pretend not to wait for carrier. That means that the
* connection is available now, and does not wait for carrier.
*
* Since the flags increase the availability of a connection, when checking
* ALL&~WAITING_CARRIER, it means that we certainly would wait for carrier. */
return FALSE;
}
/* The activation request must wait for carrier. */
return TRUE;
}
void
nm_device_disconnect_active_connection(NMActiveConnection *active,
NMDeviceStateReason device_reason,
NMActiveConnectionStateReason active_reason)
{
NMDevice *self;
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_ACTIVE_CONNECTION(active));
self = nm_active_connection_get_device(active);
if (!self) {
/* hm, no device? Just fail the active connection. */
goto do_fail;
}
priv = NM_DEVICE_GET_PRIVATE(self);
if (NM_ACTIVE_CONNECTION(priv->queued_act_request) == active) {
_clear_queued_act_request(priv, active_reason);
return;
}
if (NM_ACTIVE_CONNECTION(priv->act_request.obj) == active) {
if (priv->state < NM_DEVICE_STATE_DEACTIVATING) {
/* When the user actively deactivates a profile, we set
* the sys-iface-state to managed so that we deconfigure/cleanup the interface.
* But for external connections that go down otherwise, we don't want to touch the interface. */
if (nm_device_sys_iface_state_is_external(self))
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
nm_device_state_changed(self, NM_DEVICE_STATE_DEACTIVATING, device_reason);
} else {
/* @active is the current ac of @self, but it's going down already.
* Nothing to do. */
}
return;
}
/* the active connection references this device, but it's neither the
* queued_act_request nor the current act_request. Just set it to fail... */
do_fail:
nm_active_connection_set_state_fail(active, active_reason, NULL);
}
void
nm_device_queue_activation(NMDevice *self, NMActRequest *req)
{
NMDevicePrivate *priv;
gboolean must_queue;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IS_ACT_REQUEST(req));
nm_keep_alive_arm(nm_active_connection_get_keep_alive(NM_ACTIVE_CONNECTION(req)));
if (nm_active_connection_get_state(NM_ACTIVE_CONNECTION(req))
>= NM_ACTIVE_CONNECTION_STATE_DEACTIVATING) {
/* it's already deactivating. Nothing to do. */
nm_assert(
NM_IN_SET(nm_active_connection_get_device(NM_ACTIVE_CONNECTION(req)), NULL, self));
return;
}
nm_assert(self == nm_active_connection_get_device(NM_ACTIVE_CONNECTION(req)));
priv = NM_DEVICE_GET_PRIVATE(self);
must_queue = _carrier_wait_check_act_request_must_queue(self, req);
if (!priv->act_request.obj && !must_queue && nm_device_is_real(self)) {
_device_activate(self, req);
return;
}
/* supersede any already-queued request */
_clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
priv->queued_act_request = g_object_ref(req);
priv->queued_act_request_is_waiting_for_carrier = must_queue;
_LOGD(LOGD_DEVICE,
"queue activation request waiting for %s",
must_queue ? "carrier" : "currently active connection to disconnect");
/* Deactivate existing activation request first */
if (priv->act_request.obj) {
_LOGI(LOGD_DEVICE, "disconnecting for new activation request.");
nm_device_state_changed(self,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_NEW_ACTIVATION);
}
}
/*
* nm_device_is_activating
*
* Return whether or not the device is currently activating itself.
*
*/
gboolean
nm_device_is_activating(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceState state;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
state = nm_device_get_state(self);
if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_SECONDARIES)
return TRUE;
/* There's a small race between the time when stage 1 is scheduled
* and when the device actually sets STATE_PREPARE when the activation
* handler is actually run. If there's an activation handler scheduled
* we're activating anyway.
*/
return !!priv->activation_idle_source;
}
NMDhcpConfig *
nm_device_get_dhcp_config(NMDevice *self, int addr_family)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->ipdhcp_data_x[NM_IS_IPv4(addr_family)].config;
}
NML3Cfg *
nm_device_get_l3cfg(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->l3cfg;
}
const NML3ConfigData *
nm_device_get_l3cd(NMDevice *self, gboolean get_commited)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->l3cfg)
return NULL;
return nm_l3cfg_get_combined_l3cd(priv->l3cfg, get_commited);
}
/*****************************************************************************/
static gboolean
_dispatcher_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->dispatcher.call_id)
return FALSE;
nm_dispatcher_call_cancel(g_steal_pointer(&priv->dispatcher.call_id));
priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
return TRUE;
}
static void
_dispatcher_complete_proceed_state(NMDispatcherCallId *call_id, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
g_return_if_fail(call_id == priv->dispatcher.call_id);
priv->dispatcher.call_id = NULL;
nm_device_queue_state(self, priv->dispatcher.post_state, priv->dispatcher.post_state_reason);
priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
}
/*****************************************************************************/
static void
ip_check_pre_up(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (_dispatcher_cleanup(self))
nm_assert_not_reached();
priv->dispatcher.post_state = NM_DEVICE_STATE_SECONDARIES;
priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
if (!nm_dispatcher_call_device(NM_DISPATCHER_ACTION_PRE_UP,
self,
NULL,
_dispatcher_complete_proceed_state,
self,
&priv->dispatcher.call_id)) {
/* Just proceed on errors */
_dispatcher_complete_proceed_state(0, self);
}
}
static void
ip_check_gw_ping_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_g_source(&priv->gw_ping.watch);
nm_clear_g_source(&priv->gw_ping.timeout);
if (priv->gw_ping.pid) {
nm_utils_kill_child_async(priv->gw_ping.pid,
SIGTERM,
priv->gw_ping.log_domain,
"ping",
1000,
NULL,
NULL);
priv->gw_ping.pid = 0;
}
nm_clear_g_free(&priv->gw_ping.binary);
nm_clear_g_free(&priv->gw_ping.address);
}
static gboolean
spawn_ping(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free char *str_timeout = NULL;
gs_free char *tmp_str = NULL;
const char *args[] = {priv->gw_ping.binary,
"-I",
nm_device_get_ip_iface(self),
"-c",
"1",
"-w",
NULL,
priv->gw_ping.address,
NULL};
gs_free_error GError *error = NULL;
gboolean ret;
args[6] = str_timeout = g_strdup_printf("%u", priv->gw_ping.deadline);
tmp_str = g_strjoinv(" ", (char **) args);
_LOGD(priv->gw_ping.log_domain, "ping: running '%s'", tmp_str);
ret = g_spawn_async("/",
(char **) args,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&priv->gw_ping.pid,
&error);
if (!ret) {
_LOGW(priv->gw_ping.log_domain,
"ping: could not spawn %s: %s",
priv->gw_ping.binary,
error->message);
}
return ret;
}
static gboolean
respawn_ping_cb(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->gw_ping.watch = 0;
if (spawn_ping(self)) {
priv->gw_ping.watch = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self);
} else {
ip_check_gw_ping_cleanup(self);
ip_check_pre_up(self);
}
return FALSE;
}
static void
ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMLogDomain log_domain = priv->gw_ping.log_domain;
gboolean success = FALSE;
if (!priv->gw_ping.watch)
return;
priv->gw_ping.watch = 0;
priv->gw_ping.pid = 0;
if (WIFEXITED(status)) {
if (WEXITSTATUS(status) == 0) {
_LOGD(log_domain, "ping: gateway ping succeeded");
success = TRUE;
} else {
_LOGW(log_domain, "ping: gateway ping failed with error code %d", WEXITSTATUS(status));
}
} else
_LOGW(log_domain, "ping: stopped unexpectedly with status %d", status);
if (success) {
/* We've got connectivity, proceed to pre_up */
ip_check_gw_ping_cleanup(self);
ip_check_pre_up(self);
} else {
/* If ping exited with an error it may have returned early,
* wait 1 second and restart it */
priv->gw_ping.watch = g_timeout_add_seconds(1, respawn_ping_cb, self);
}
}
static gboolean
ip_check_ping_timeout_cb(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->gw_ping.timeout = 0;
_LOGW(priv->gw_ping.log_domain, "ping: gateway ping timed out");
ip_check_gw_ping_cleanup(self);
ip_check_pre_up(self);
return FALSE;
}
static gboolean
start_ping(NMDevice *self,
NMLogDomain log_domain,
const char *binary,
const char *address,
guint timeout)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
g_return_val_if_fail(priv->gw_ping.watch == 0, FALSE);
g_return_val_if_fail(priv->gw_ping.timeout == 0, FALSE);
priv->gw_ping.log_domain = log_domain;
priv->gw_ping.address = g_strdup(address);
priv->gw_ping.binary = g_strdup(binary);
priv->gw_ping.deadline = timeout + 10; /* the proper termination is enforced by a timer */
if (spawn_ping(self)) {
priv->gw_ping.watch = g_child_watch_add(priv->gw_ping.pid, ip_check_ping_watch_cb, self);
priv->gw_ping.timeout = g_timeout_add_seconds(timeout, ip_check_ping_timeout_cb, self);
return TRUE;
}
ip_check_gw_ping_cleanup(self);
return FALSE;
}
static void
nm_device_start_ip_check(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection *connection;
NMSettingConnection *s_con;
guint timeout = 0;
const char *ping_binary = NULL;
char buf[NM_UTILS_INET_ADDRSTRLEN];
NMLogDomain log_domain = LOGD_IP4;
/* Shouldn't be any active ping here, since IP_CHECK happens after the
* first IP method completes. Any subsequently completing IP method doesn't
* get checked.
*/
g_return_if_fail(!priv->gw_ping.watch);
g_return_if_fail(!priv->gw_ping.timeout);
g_return_if_fail(!priv->gw_ping.pid);
g_return_if_fail(priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY
|| priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY);
connection = nm_device_get_applied_connection(self);
g_assert(connection);
s_con = nm_connection_get_setting_connection(connection);
g_assert(s_con);
timeout = nm_setting_connection_get_gateway_ping_timeout(s_con);
buf[0] = '\0';
if (timeout) {
const NMPObject *gw;
const NML3ConfigData *l3cd;
l3cd = priv->l3cfg ? nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE) : NULL;
if (!l3cd) {
/* pass */
} else if (priv->ip_data_4.state == NM_DEVICE_IP_STATE_READY) {
gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET);
if (gw) {
_nm_utils_inet4_ntop(NMP_OBJECT_CAST_IP4_ROUTE(gw)->gateway, buf);
ping_binary = nm_utils_find_helper("ping", "/usr/bin/ping", NULL);
log_domain = LOGD_IP4;
}
} else if (priv->ip_data_6.state == NM_DEVICE_IP_STATE_READY) {
gw = nm_l3_config_data_get_best_default_route(l3cd, AF_INET6);
if (gw) {
_nm_utils_inet6_ntop(&NMP_OBJECT_CAST_IP6_ROUTE(gw)->gateway, buf);
ping_binary = nm_utils_find_helper("ping6", "/usr/bin/ping6", NULL);
log_domain = LOGD_IP6;
}
}
}
if (buf[0])
start_ping(self, log_domain, ping_binary, buf, timeout);
/* If no ping was started, just advance to pre_up */
if (!priv->gw_ping.pid)
ip_check_pre_up(self);
}
/*****************************************************************************/
static gboolean
carrier_wait_timeout(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->carrier_wait_id = 0;
nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
if (!priv->carrier)
_carrier_wait_check_queued_act_request(self);
return G_SOURCE_REMOVE;
}
static gboolean
nm_device_is_up(NMDevice *self)
{
int ifindex;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
ifindex = nm_device_get_ip_ifindex(self);
return ifindex > 0 ? nm_platform_link_is_up(nm_device_get_platform(self), ifindex) : TRUE;
}
static gint64
_get_carrier_wait_ms(NMDevice *self)
{
return nm_config_data_get_device_config_int64(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
self,
10,
0,
G_MAXINT32,
CARRIER_WAIT_TIME_MS,
CARRIER_WAIT_TIME_MS);
}
gboolean
nm_device_bring_up(NMDevice *self, gboolean block, gboolean *no_firmware)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean device_is_up = FALSE;
NMDeviceCapabilities capabilities;
int ifindex;
int r;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
NM_SET_OUT(no_firmware, FALSE);
if (!nm_device_get_enabled(self)) {
_LOGD(LOGD_PLATFORM, "bringing up device ignored due to disabled");
return FALSE;
}
ifindex = nm_device_get_ip_ifindex(self);
_LOGD(LOGD_PLATFORM, "bringing up device %d", ifindex);
if (ifindex <= 0) {
/* assume success. */
} else {
r = nm_platform_link_change_flags(nm_device_get_platform(self), ifindex, IFF_UP, TRUE);
NM_SET_OUT(no_firmware, (r == -NME_PL_NO_FIRMWARE));
if (r < 0)
return FALSE;
}
/* Store carrier immediately. */
nm_device_set_carrier_from_platform(self);
device_is_up = nm_device_is_up(self);
if (block && !device_is_up) {
gint64 wait_until = nm_utils_get_monotonic_timestamp_usec() + 10000 /* microseconds */;
do {
g_usleep(200);
if (!nm_platform_link_refresh(nm_device_get_platform(self), ifindex))
return FALSE;
device_is_up = nm_device_is_up(self);
} while (!device_is_up && nm_utils_get_monotonic_timestamp_usec() < wait_until);
}
if (!device_is_up) {
if (block)
_LOGW(LOGD_PLATFORM, "device not up after timeout!");
else
_LOGD(LOGD_PLATFORM, "device not up immediately");
return FALSE;
}
/* some ethernet devices fail to report capabilities unless the device
* is up. Re-read the capabilities. */
capabilities = 0;
if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities)
capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self);
_add_capabilities(self, capabilities);
/* Devices that support carrier detect must be IFF_UP to report carrier
* changes; so after setting the device IFF_UP we must suppress startup
* complete (via a pending action) until either the carrier turns on, or
* a timeout is reached.
*/
if (nm_device_has_capability(self, NM_DEVICE_CAP_CARRIER_DETECT)) {
gint64 now_ms, until_ms;
/* we start a grace period of 5 seconds during which we will schedule
* a pending action whenever we have no carrier.
*
* If during that time carrier goes away, we declare the interface
* as not ready. */
nm_clear_g_source(&priv->carrier_wait_id);
if (!priv->carrier)
nm_device_add_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
now_ms = nm_utils_get_monotonic_timestamp_msec();
until_ms = NM_MAX(now_ms + _get_carrier_wait_ms(self), priv->carrier_wait_until_ms);
priv->carrier_wait_id = g_timeout_add(until_ms - now_ms, carrier_wait_timeout, self);
}
/* Can only get HW address of some devices when they are up */
nm_device_update_hw_address(self);
_dev_l3_cfg_commit(self, TRUE);
return TRUE;
}
void
nm_device_take_down(NMDevice *self, gboolean block)
{
int ifindex;
gboolean device_is_up;
g_return_if_fail(NM_IS_DEVICE(self));
ifindex = nm_device_get_ip_ifindex(self);
_LOGD(LOGD_PLATFORM, "taking down device %d", ifindex);
if (ifindex <= 0) {
/* devices without ifindex are always up. */
return;
}
if (!nm_platform_link_change_flags(nm_device_get_platform(self), ifindex, IFF_UP, FALSE))
return;
device_is_up = nm_device_is_up(self);
if (block && device_is_up) {
gint64 wait_until = nm_utils_get_monotonic_timestamp_usec() + 10000 /* microseconds */;
do {
g_usleep(200);
if (!nm_platform_link_refresh(nm_device_get_platform(self), ifindex))
return;
device_is_up = nm_device_is_up(self);
} while (device_is_up && nm_utils_get_monotonic_timestamp_usec() < wait_until);
}
if (device_is_up) {
if (block)
_LOGW(LOGD_PLATFORM, "device not down after timeout!");
else
_LOGD(LOGD_PLATFORM, "device not down immediately");
}
}
void
nm_device_set_firmware_missing(NMDevice *self, gboolean new_missing)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->firmware_missing != new_missing) {
priv->firmware_missing = new_missing;
_notify(self, PROP_FIRMWARE_MISSING);
}
}
gboolean
nm_device_get_firmware_missing(NMDevice *self)
{
return NM_DEVICE_GET_PRIVATE(self)->firmware_missing;
}
/*****************************************************************************/
NM_UTILS_FLAGS2STR_DEFINE(nm_unmanaged_flags2str,
NMUnmanagedFlags,
NM_UTILS_FLAGS2STR(NM_UNMANAGED_SLEEPING, "sleeping"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_QUITTING, "quitting"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_PARENT, "parent"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_BY_TYPE, "by-type"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_PLATFORM_INIT, "platform-init"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_EXPLICIT, "user-explicit"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_BY_DEFAULT, "by-default"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_SETTINGS, "user-settings"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_CONF, "user-conf"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_USER_UDEV, "user-udev"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_EXTERNAL_DOWN, "external-down"),
NM_UTILS_FLAGS2STR(NM_UNMANAGED_IS_SLAVE, "is-slave"), );
static const char *
_unmanaged_flags2str(NMUnmanagedFlags flags, NMUnmanagedFlags mask, char *buf, gsize len)
{
char buf2[512];
char *b;
char *tmp, *tmp2;
gsize l;
nm_utils_to_string_buffer_init(&buf, &len);
if (!len)
return buf;
b = buf;
mask |= flags;
nm_unmanaged_flags2str(flags, b, len);
l = strlen(b);
b += l;
len -= l;
nm_unmanaged_flags2str(mask & ~flags, buf2, sizeof(buf2));
if (buf2[0]) {
gboolean add_separator = l > 0;
tmp = buf2;
while (TRUE) {
if (add_separator)
nm_strbuf_append_c(&b, &len, ',');
add_separator = TRUE;
tmp2 = strchr(tmp, ',');
if (tmp2)
tmp2[0] = '\0';
nm_strbuf_append_c(&b, &len, '!');
nm_strbuf_append_str(&b, &len, tmp);
if (!tmp2)
break;
tmp = &tmp2[1];
}
}
return buf;
}
static gboolean
_get_managed_by_flags(NMUnmanagedFlags flags, NMUnmanagedFlags mask, gboolean for_user_request)
{
/* Evaluate the managed state based on the unmanaged flags.
*
* Some flags are authoritative, meaning they always cause
* the device to be unmanaged (e.g. @NM_UNMANAGED_PLATFORM_INIT).
*
* OTOH, some flags can be overwritten. For example NM_UNMANAGED_USER_UDEV
* is ignored once NM_UNMANAGED_USER_EXPLICIT is set. The idea is that
* the flag from the configuration has no effect once the user explicitly
* touches the unmanaged flags. */
if (for_user_request) {
/* @for_user_request can make the result only ~more~ managed.
* If the flags already indicate a managed state for a non-user-request,
* then it is also managed for an explicit user-request.
*
* Effectively, this check is redundant, as the code below already
* already ensures that. Still, express this invariant explicitly here. */
if (_get_managed_by_flags(flags, mask, FALSE))
return TRUE;
/* A for-user-request, is effectively the same as pretending
* that user-explicit flag is cleared. */
mask |= NM_UNMANAGED_USER_EXPLICIT;
flags &= ~NM_UNMANAGED_USER_EXPLICIT;
}
if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_SETTINGS)
&& !NM_FLAGS_ANY(flags, NM_UNMANAGED_USER_SETTINGS)) {
/* NM_UNMANAGED_USER_SETTINGS can only explicitly unmanage a device. It cannot
* *manage* it. Having NM_UNMANAGED_USER_SETTINGS explicitly not set, is the
* same as having it not set at all. */
mask &= ~NM_UNMANAGED_USER_SETTINGS;
}
if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_UDEV)) {
/* configuration from udev or nm-config overwrites the by-default flag
* which is based on the device type.
* configuration from udev overwrites external-down */
flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_EXTERNAL_DOWN);
}
if (NM_FLAGS_ANY(mask, NM_UNMANAGED_USER_CONF)) {
/* configuration from NetworkManager.conf overwrites the by-default flag
* which is based on the device type.
* It also overwrites the udev configuration and external-down */
flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_EXTERNAL_DOWN);
}
if (NM_FLAGS_HAS(mask, NM_UNMANAGED_IS_SLAVE) && !NM_FLAGS_HAS(flags, NM_UNMANAGED_IS_SLAVE)) {
/* for an enslaved device, by-default doesn't matter */
flags &= ~NM_UNMANAGED_BY_DEFAULT;
}
if (NM_FLAGS_HAS(mask, NM_UNMANAGED_USER_EXPLICIT)) {
/* if the device is managed by user-decision, certain other flags
* are ignored. */
flags &= ~(NM_UNMANAGED_BY_DEFAULT | NM_UNMANAGED_USER_UDEV | NM_UNMANAGED_USER_CONF
| NM_UNMANAGED_EXTERNAL_DOWN);
}
return flags == NM_UNMANAGED_NONE;
}
/**
* nm_device_get_managed:
* @self: the #NMDevice
* @for_user_request: whether to check the flags for an explicit user-request
* Setting this to %TRUE has the same effect as if %NM_UNMANAGED_USER_EXPLICIT
* unmanaged flag would be unset (meaning: explicitly not-unmanaged).
* If this parameter is %TRUE, the device can only appear more managed.
*
* Whether the device is unmanaged according to the unmanaged flags.
*
* Returns: %TRUE if the device is unmanaged because of the flags.
*/
gboolean
nm_device_get_managed(NMDevice *self, gboolean for_user_request)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
if (!nm_device_is_real(self)) {
/* a unrealized device is always considered unmanaged. */
return FALSE;
}
priv = NM_DEVICE_GET_PRIVATE(self);
return _get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, for_user_request);
}
/**
* nm_device_get_unmanaged_mask:
* @self: the #NMDevice
* @flag: the unmanaged flags to check.
*
* Return the unmanaged flags mask set on this device.
*
* Returns: the flags of the device ( & @flag)
*/
NMUnmanagedFlags
nm_device_get_unmanaged_mask(NMDevice *self, NMUnmanagedFlags flag)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_UNMANAGED_NONE);
g_return_val_if_fail(flag != NM_UNMANAGED_NONE, NM_UNMANAGED_NONE);
return NM_DEVICE_GET_PRIVATE(self)->unmanaged_mask & flag;
}
/**
* nm_device_get_unmanaged_flags:
* @self: the #NMDevice
* @flag: the unmanaged flags to check.
*
* Return the unmanaged flags of the device.
*
* Returns: the flags of the device ( & @flag)
*/
NMUnmanagedFlags
nm_device_get_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flag)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_UNMANAGED_NONE);
g_return_val_if_fail(flag != NM_UNMANAGED_NONE, NM_UNMANAGED_NONE);
return NM_DEVICE_GET_PRIVATE(self)->unmanaged_flags & flag;
}
/**
* _set_unmanaged_flags:
* @self: the #NMDevice instance
* @flags: which #NMUnmanagedFlags to set.
* @set_op: whether to set/clear/forget the flags. You can also pass
* boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED
* and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively.
* @allow_state_transition: if %FALSE, setting flags never triggers a device
* state change. If %TRUE, the device can change state, if it is real and
* switches from managed to unmanaged (or vice versa).
* @now: whether the state change should be immediate or delayed
* @reason: the device state reason passed to nm_device_state_changed() if
* the device becomes managed/unmanaged. This is only relevant if the
* device switches state and if @allow_state_transition is %TRUE.
*
* Set the unmanaged flags of the device.
**/
static void
_set_unmanaged_flags(NMDevice *self,
NMUnmanagedFlags flags,
NMUnmanFlagOp set_op,
gboolean allow_state_transition,
gboolean now,
NMDeviceStateReason reason)
{
NMDevicePrivate *priv;
gboolean was_managed, transition_state;
NMUnmanagedFlags old_flags, old_mask;
NMDeviceState new_state;
const char *operation = NULL;
char str1[512];
char str2[512];
gboolean do_notify_has_pending_actions = FALSE;
gboolean had_pending_actions = FALSE;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(flags);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->real)
allow_state_transition = FALSE;
was_managed = allow_state_transition && nm_device_get_managed(self, FALSE);
if (NM_FLAGS_HAS(priv->unmanaged_flags, NM_UNMANAGED_PLATFORM_INIT)
&& NM_FLAGS_HAS(flags, NM_UNMANAGED_PLATFORM_INIT)
&& NM_IN_SET(set_op, NM_UNMAN_FLAG_OP_SET_MANAGED)) {
/* we are clearing the platform-init flags. This triggers additional actions. */
if (!NM_FLAGS_HAS(flags, NM_UNMANAGED_USER_SETTINGS)) {
gboolean unmanaged;
unmanaged = nm_device_spec_match_list(
self,
nm_settings_get_unmanaged_specs(NM_DEVICE_GET_PRIVATE(self)->settings));
nm_device_set_unmanaged_flags(self, NM_UNMANAGED_USER_SETTINGS, !!unmanaged);
}
if (priv->pending_actions.len == 0) {
do_notify_has_pending_actions = TRUE;
had_pending_actions = nm_device_has_pending_action(self);
}
}
old_flags = priv->unmanaged_flags;
old_mask = priv->unmanaged_mask;
switch (set_op) {
case NM_UNMAN_FLAG_OP_FORGET:
priv->unmanaged_mask &= ~flags;
priv->unmanaged_flags &= ~flags;
operation = "forget";
break;
case NM_UNMAN_FLAG_OP_SET_UNMANAGED:
priv->unmanaged_mask |= flags;
priv->unmanaged_flags |= flags;
operation = "set-unmanaged";
break;
case NM_UNMAN_FLAG_OP_SET_MANAGED:
priv->unmanaged_mask |= flags;
priv->unmanaged_flags &= ~flags;
operation = "set-managed";
break;
default:
g_return_if_reached();
}
if (old_flags == priv->unmanaged_flags && old_mask == priv->unmanaged_mask)
return;
transition_state =
allow_state_transition && was_managed != nm_device_get_managed(self, FALSE)
&& (was_managed
|| (!was_managed && nm_device_get_state(self) == NM_DEVICE_STATE_UNMANAGED));
_LOGD(LOGD_DEVICE,
"unmanaged: flags set to [%s%s0x%0x/0x%x/%s%s], %s [%s=0x%0x]%s%s%s)",
_unmanaged_flags2str(priv->unmanaged_flags, priv->unmanaged_mask, str1, sizeof(str1)),
(priv->unmanaged_flags | priv->unmanaged_mask) ? "=" : "",
(guint) priv->unmanaged_flags,
(guint) priv->unmanaged_mask,
(_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, FALSE)
? "managed"
: (_get_managed_by_flags(priv->unmanaged_flags, priv->unmanaged_mask, TRUE)
? "manageable"
: "unmanaged")),
priv->real ? "" : "/unrealized",
operation,
nm_unmanaged_flags2str(flags, str2, sizeof(str2)),
flags,
NM_PRINT_FMT_QUOTED(allow_state_transition,
", reason ",
nm_device_state_reason_to_string_a(reason),
transition_state ? ", transition-state" : "",
""));
if (do_notify_has_pending_actions && had_pending_actions != nm_device_has_pending_action(self))
_notify(self, PROP_HAS_PENDING_ACTION);
if (transition_state) {
new_state = was_managed ? NM_DEVICE_STATE_UNMANAGED : NM_DEVICE_STATE_UNAVAILABLE;
if (now)
nm_device_state_changed(self, new_state, reason);
else
nm_device_queue_state(self, new_state, reason);
}
}
/**
* @self: the #NMDevice instance
* @flags: which #NMUnmanagedFlags to set.
* @set_op: whether to set/clear/forget the flags. You can also pass
* boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED
* and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively.
*
* Set the unmanaged flags of the device (does not trigger a state change).
**/
void
nm_device_set_unmanaged_flags(NMDevice *self, NMUnmanagedFlags flags, NMUnmanFlagOp set_op)
{
_set_unmanaged_flags(self, flags, set_op, FALSE, FALSE, NM_DEVICE_STATE_REASON_NONE);
}
/**
* nm_device_set_unmanaged_by_flags:
* @self: the #NMDevice instance
* @flags: which #NMUnmanagedFlags to set.
* @set_op: whether to set/clear/forget the flags. You can also pass
* boolean values %TRUE and %FALSE, which mean %NM_UNMAN_FLAG_OP_SET_UNMANAGED
* and %NM_UNMAN_FLAG_OP_SET_MANAGED, respectively.
* @reason: the device state reason passed to nm_device_state_changed() if
* the device becomes managed/unmanaged.
*
* Set the unmanaged flags of the device and possibly trigger a state change.
**/
void
nm_device_set_unmanaged_by_flags(NMDevice *self,
NMUnmanagedFlags flags,
NMUnmanFlagOp set_op,
NMDeviceStateReason reason)
{
_set_unmanaged_flags(self, flags, set_op, TRUE, TRUE, reason);
}
void
nm_device_set_unmanaged_by_flags_queue(NMDevice *self,
NMUnmanagedFlags flags,
NMUnmanFlagOp set_op,
NMDeviceStateReason reason)
{
_set_unmanaged_flags(self, flags, set_op, TRUE, FALSE, reason);
}
/**
* nm_device_check_unrealized_device_managed:
*
* Checks if a unrealized device is managed from user settings
* or user configuration.
*/
gboolean
nm_device_check_unrealized_device_managed(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(!nm_device_is_real(self));
if (!nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
self,
TRUE,
TRUE))
return FALSE;
if (nm_device_spec_match_list(self, nm_settings_get_unmanaged_specs(priv->settings)))
return FALSE;
return TRUE;
}
void
nm_device_set_unmanaged_by_user_settings(NMDevice *self, gboolean now)
{
gboolean unmanaged;
g_return_if_fail(NM_IS_DEVICE(self));
if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
/* the device is already unmanaged due to platform-init.
*
* We want to delay evaluating the device spec, because it will freeze
* the permanent MAC address. That should not be done, before the platform
* link is fully initialized (via UDEV).
*
* Note that when clearing NM_UNMANAGED_PLATFORM_INIT, we will re-evaluate
* whether the device is unmanaged by user-settings. */
return;
}
unmanaged = nm_device_spec_match_list(
self,
nm_settings_get_unmanaged_specs(NM_DEVICE_GET_PRIVATE(self)->settings));
_set_unmanaged_flags(self,
NM_UNMANAGED_USER_SETTINGS,
!!unmanaged,
TRUE,
now,
unmanaged ? NM_DEVICE_STATE_REASON_NOW_UNMANAGED
: NM_DEVICE_STATE_REASON_NOW_MANAGED);
}
void
nm_device_set_unmanaged_by_user_udev(NMDevice *self)
{
int ifindex;
gboolean platform_unmanaged = FALSE;
ifindex = self->_priv->ifindex;
if (ifindex <= 0
|| !nm_platform_link_get_unmanaged(nm_device_get_platform(self),
ifindex,
&platform_unmanaged))
return;
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_USER_UDEV,
platform_unmanaged,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
void
nm_device_set_unmanaged_by_user_conf(NMDevice *self)
{
gboolean value;
NMUnmanFlagOp set_op;
value = nm_config_data_get_device_config_boolean(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_MANAGED,
self,
-1,
TRUE);
switch (value) {
case TRUE:
set_op = NM_UNMAN_FLAG_OP_SET_MANAGED;
break;
case FALSE:
set_op = NM_UNMAN_FLAG_OP_SET_UNMANAGED;
break;
default:
set_op = NM_UNMAN_FLAG_OP_FORGET;
break;
}
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_USER_CONF,
set_op,
NM_DEVICE_STATE_REASON_USER_REQUESTED);
}
void
nm_device_set_unmanaged_by_quitting(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean need_deactivate =
nm_device_is_activating(self) || priv->state == NM_DEVICE_STATE_ACTIVATED;
/* It's OK to block here because we're quitting */
if (need_deactivate)
_set_state_full(self,
NM_DEVICE_STATE_DEACTIVATING,
NM_DEVICE_STATE_REASON_NOW_UNMANAGED,
TRUE);
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_QUITTING,
TRUE,
need_deactivate ? NM_DEVICE_STATE_REASON_REMOVED
: NM_DEVICE_STATE_REASON_NOW_UNMANAGED);
}
/*****************************************************************************/
void
nm_device_reapply_settings_immediately(NMDevice *self)
{
NMConnection *applied_connection;
NMSettingsConnection *settings_connection;
NMDeviceState state;
NMSettingConnection *s_con_settings;
NMSettingConnection *s_con_applied;
const char *zone;
NMMetered metered;
guint64 version_id;
g_return_if_fail(NM_IS_DEVICE(self));
state = nm_device_get_state(self);
if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED)
return;
applied_connection = nm_device_get_applied_connection(self);
settings_connection = nm_device_get_settings_connection(self);
if (!nm_settings_connection_has_unmodified_applied_connection(
settings_connection,
applied_connection,
NM_SETTING_COMPARE_FLAG_IGNORE_REAPPLY_IMMEDIATELY))
return;
s_con_settings = nm_connection_get_setting_connection(
nm_settings_connection_get_connection(settings_connection));
s_con_applied = nm_connection_get_setting_connection(applied_connection);
if (!nm_streq0((zone = nm_setting_connection_get_zone(s_con_settings)),
nm_setting_connection_get_zone(s_con_applied))) {
version_id = nm_active_connection_version_id_bump(
(NMActiveConnection *) self->_priv->act_request.obj);
_LOGD(LOGD_DEVICE,
"reapply setting: zone = %s%s%s (version-id %llu)",
NM_PRINT_FMT_QUOTE_STRING(zone),
(unsigned long long) version_id);
g_object_set(G_OBJECT(s_con_applied), NM_SETTING_CONNECTION_ZONE, zone, NULL);
nm_device_update_firewall_zone(self);
}
if ((metered = nm_setting_connection_get_metered(s_con_settings))
!= nm_setting_connection_get_metered(s_con_applied)) {
version_id = nm_active_connection_version_id_bump(
(NMActiveConnection *) self->_priv->act_request.obj);
_LOGD(LOGD_DEVICE,
"reapply setting: metered = %d (version-id %llu)",
(int) metered,
(unsigned long long) version_id);
g_object_set(G_OBJECT(s_con_applied), NM_SETTING_CONNECTION_METERED, metered, NULL);
nm_device_update_metered(self);
}
}
void
nm_device_update_firewall_zone(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->fw_state >= FIREWALL_STATE_INITIALIZED
&& !nm_device_sys_iface_state_is_external(self))
fw_change_zone(self);
}
void
nm_device_update_metered(NMDevice *self)
{
#define NM_METERED_INVALID ((NMMetered) -1)
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingConnection *setting;
NMMetered conn_value, value = NM_METERED_INVALID;
NMConnection *connection = NULL;
NMDeviceState state;
g_return_if_fail(NM_IS_DEVICE(self));
state = nm_device_get_state(self);
if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_ACTIVATED)
value = NM_METERED_UNKNOWN;
if (value == NM_METERED_INVALID) {
connection = nm_device_get_applied_connection(self);
if (connection) {
setting = nm_connection_get_setting_connection(connection);
if (setting) {
conn_value = nm_setting_connection_get_metered(setting);
if (conn_value != NM_METERED_UNKNOWN)
value = conn_value;
}
}
}
if (value == NM_METERED_INVALID && NM_DEVICE_GET_CLASS(self)->get_guessed_metered
&& NM_DEVICE_GET_CLASS(self)->get_guessed_metered(self))
value = NM_METERED_GUESS_YES;
/* Try to guess a value using the metered flag in IP configuration */
if (value == NM_METERED_INVALID) {
if (priv->l3cfg) {
const NML3ConfigData *l3cd;
l3cd = nm_l3cfg_get_combined_l3cd(priv->l3cfg, TRUE);
if (l3cd && nm_l3_config_data_get_metered(l3cd) == NM_TERNARY_TRUE)
value = NM_METERED_GUESS_YES;
}
}
/* Otherwise, look at connection type. For Bluetooth, we look at the type of
* Bluetooth sharing: for PANU/DUN (where we are receiving internet from
* another device) we set GUESS_YES; for NAP (where we are sharing internet
* to another device) we set GUESS_NO. We ignore WiMAX here as its no
* longer supported by NetworkManager. */
if (value == NM_METERED_INVALID
&& nm_connection_is_type(connection, NM_SETTING_BLUETOOTH_SETTING_NAME)) {
if (_nm_connection_get_setting_bluetooth_for_nap(connection)) {
/* NAP types are not metered, but other types are. */
value = NM_METERED_GUESS_NO;
} else
value = NM_METERED_GUESS_YES;
}
if (value == NM_METERED_INVALID) {
if (nm_connection_is_type(connection, NM_SETTING_GSM_SETTING_NAME)
|| nm_connection_is_type(connection, NM_SETTING_CDMA_SETTING_NAME))
value = NM_METERED_GUESS_YES;
else
value = NM_METERED_GUESS_NO;
}
if (value != priv->metered) {
_LOGD(LOGD_DEVICE, "set metered value %d", value);
priv->metered = value;
_notify(self, PROP_METERED);
}
}
static NMDeviceCheckDevAvailableFlags
_device_check_dev_available_flags_from_con(NMDeviceCheckConAvailableFlags con_flags)
{
NMDeviceCheckDevAvailableFlags dev_flags;
dev_flags = NM_DEVICE_CHECK_DEV_AVAILABLE_NONE;
if (NM_FLAGS_HAS(con_flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER))
dev_flags |= _NM_DEVICE_CHECK_DEV_AVAILABLE_IGNORE_CARRIER;
return dev_flags;
}
static gboolean
_nm_device_check_connection_available(NMDevice *self,
NMConnection *connection,
NMDeviceCheckConAvailableFlags flags,
const char *specific_object,
GError **error)
{
NMDeviceState state;
GError *local = NULL;
/* an unrealized software device is always available, hardware devices never. */
if (!nm_device_is_real(self)) {
if (nm_device_is_software(self)) {
if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) {
if (error) {
g_return_val_if_fail(local, FALSE);
nm_utils_error_set(error,
local->domain == NM_UTILS_ERROR ? local->code
: NM_UTILS_ERROR_UNKNOWN,
"profile is not compatible with software device (%s)",
local->message);
g_error_free(local);
}
return FALSE;
}
return TRUE;
}
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
"hardware device is not realized");
return FALSE;
}
state = nm_device_get_state(self);
if (state < NM_DEVICE_STATE_UNMANAGED) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
"device is in unknown state");
return FALSE;
}
if (state < NM_DEVICE_STATE_UNAVAILABLE) {
if (nm_device_get_managed(self, FALSE)) {
/* device is managed, both for user-requests and non-user-requests alike. */
} else {
if (!nm_device_get_managed(self, TRUE)) {
/* device is strictly unmanaged by authoritative unmanaged reasons. */
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
"device is strictly unmanaged");
return FALSE;
}
if (!NM_FLAGS_HAS(flags,
_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_OVERRULE_UNMANAGED)) {
/* device could be managed for an explict user-request, but this is not such a request. */
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_UNMANAGED_DEVICE,
"device is currently unmanaged");
return FALSE;
}
}
}
if (state < NM_DEVICE_STATE_DISCONNECTED && !nm_device_is_software(self)) {
if (!nm_device_is_available(self, _device_check_dev_available_flags_from_con(flags))) {
if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST)) {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device is not available");
} else {
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device is not available for internal request");
}
return FALSE;
}
}
if (!nm_device_check_connection_compatible(self, connection, error ? &local : NULL)) {
if (error) {
nm_utils_error_set(error,
local->domain == NM_UTILS_ERROR ? local->code
: NM_UTILS_ERROR_UNKNOWN,
"profile is not compatible with device (%s)",
local->message);
g_error_free(local);
}
return FALSE;
}
return NM_DEVICE_GET_CLASS(self)->check_connection_available(self,
connection,
flags,
specific_object,
error);
}
/**
* nm_device_check_connection_available():
* @self: the #NMDevice
* @connection: the #NMConnection to check for availability
* @flags: flags to affect the decision making of whether a connection
* is available. Adding a flag can only make a connection more available,
* not less.
* @specific_object: a device type dependent argument to further
* filter the result. Passing a non %NULL specific object can only reduce
* the availability of a connection.
* @error: optionally give reason why not available.
*
* Check if @connection is available to be activated on @self.
*
* Returns: %TRUE if @connection can be activated on @self
*/
gboolean
nm_device_check_connection_available(NMDevice *self,
NMConnection *connection,
NMDeviceCheckConAvailableFlags flags,
const char *specific_object,
GError **error)
{
gboolean available;
available =
_nm_device_check_connection_available(self, connection, flags, specific_object, error);
#if NM_MORE_ASSERTS >= 2
{
/* The meaning of the flags is so that *adding* a flag relaxes a condition, thus making
* the device *more* available. Assert against that requirement by testing all the flags. */
NMDeviceCheckConAvailableFlags i, j, k;
gboolean available_all[NM_DEVICE_CHECK_CON_AVAILABLE_ALL + 1] = {FALSE};
for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++)
available_all[i] =
_nm_device_check_connection_available(self, connection, i, specific_object, NULL);
for (i = 0; i <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; i++) {
for (j = 1; j <= NM_DEVICE_CHECK_CON_AVAILABLE_ALL; j <<= 1) {
if (NM_FLAGS_ANY(i, j)) {
k = i & ~j;
nm_assert(available_all[i] == available_all[k] || available_all[i]);
}
}
}
}
#endif
return available;
}
static gboolean
available_connections_del_all(NMDevice *self)
{
if (g_hash_table_size(self->_priv->available_connections) == 0)
return FALSE;
g_hash_table_remove_all(self->_priv->available_connections);
return TRUE;
}
static gboolean
available_connections_add(NMDevice *self, NMSettingsConnection *sett_conn)
{
return g_hash_table_add(self->_priv->available_connections, g_object_ref(sett_conn));
}
static gboolean
available_connections_del(NMDevice *self, NMSettingsConnection *sett_conn)
{
return g_hash_table_remove(self->_priv->available_connections, sett_conn);
}
static gboolean
check_connection_available(NMDevice *self,
NMConnection *connection,
NMDeviceCheckConAvailableFlags flags,
const char *specific_object,
GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
/* Connections which require a network connection are not available when
* the device has no carrier, even with ignore-carrer=TRUE.
*/
if (priv->carrier || !connection_requires_carrier(connection))
return TRUE;
if (NM_FLAGS_HAS(flags, _NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST_WAITING_CARRIER)
&& priv->carrier_wait_id != 0) {
/* The device has no carrier though the connection requires it.
*
* If we are still waiting for carrier, the connection is available
* for an explicit user-request. */
return TRUE;
}
/* master types are always available even without carrier.
* Making connection non-available would un-enslave slaves which
* is not desired. */
if (nm_device_is_master(self))
return TRUE;
if (!priv->up) {
/* If the device is !IFF_UP it also has no carrier. But we assume that if we
* would start activating the device (and thereby set the device IFF_UP),
* that we would get a carrier. We only know after we set the device up,
* and we only set it up after we start activating it. So presumably, this
* profile would be available (but we just don't know). */
return TRUE;
}
nm_utils_error_set_literal(error,
NM_UTILS_ERROR_CONNECTION_AVAILABLE_TEMPORARY,
"device has no carrier");
return FALSE;
}
void
nm_device_recheck_available_connections(NMDevice *self)
{
NMDevicePrivate *priv;
NMSettingsConnection *const *connections;
gboolean changed = FALSE;
GHashTableIter h_iter;
NMSettingsConnection *sett_conn;
guint i;
gs_unref_hashtable GHashTable *prune_list = NULL;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (g_hash_table_size(priv->available_connections) > 0) {
prune_list = g_hash_table_new(nm_direct_hash, NULL);
g_hash_table_iter_init(&h_iter, priv->available_connections);
while (g_hash_table_iter_next(&h_iter, (gpointer *) &sett_conn, NULL))
g_hash_table_add(prune_list, sett_conn);
}
connections = nm_settings_get_connections(priv->settings, NULL);
for (i = 0; connections[i]; i++) {
sett_conn = connections[i];
if (nm_device_check_connection_available(self,
nm_settings_connection_get_connection(sett_conn),
NM_DEVICE_CHECK_CON_AVAILABLE_NONE,
NULL,
NULL)) {
if (available_connections_add(self, sett_conn))
changed = TRUE;
if (prune_list)
g_hash_table_remove(prune_list, sett_conn);
}
}
if (prune_list) {
g_hash_table_iter_init(&h_iter, prune_list);
while (g_hash_table_iter_next(&h_iter, (gpointer *) &sett_conn, NULL)) {
if (available_connections_del(self, sett_conn))
changed = TRUE;
}
}
if (changed)
_notify(self, PROP_AVAILABLE_CONNECTIONS);
available_connections_check_delete_unrealized(self);
}
/**
* nm_device_get_best_connection:
* @self: the #NMDevice
* @specific_object: a specific object path if any
* @error: reason why no connection was returned
*
* Returns a connection that's most suitable for user-initiated activation
* of a device, optionally with a given specific object.
*
* Returns: the #NMSettingsConnection or %NULL (setting an @error)
*/
NMSettingsConnection *
nm_device_get_best_connection(NMDevice *self, const char *specific_object, GError **error)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingsConnection *sett_conn = NULL;
NMSettingsConnection *candidate;
guint64 best_timestamp = 0;
GHashTableIter iter;
g_hash_table_iter_init(&iter, priv->available_connections);
while (g_hash_table_iter_next(&iter, (gpointer) &candidate, NULL)) {
guint64 candidate_timestamp = 0;
/* If a specific object is given, only include connections that are
* compatible with it.
*/
if (specific_object /* << Optimization: we know that the connection is available without @specific_object. */
&& !nm_device_check_connection_available(
self,
nm_settings_connection_get_connection(candidate),
_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
specific_object,
NULL))
continue;
nm_settings_connection_get_timestamp(candidate, &candidate_timestamp);
if (!sett_conn || (candidate_timestamp > best_timestamp)) {
sett_conn = candidate;
best_timestamp = candidate_timestamp;
}
}
if (!sett_conn) {
g_set_error(error,
NM_MANAGER_ERROR,
NM_MANAGER_ERROR_UNKNOWN_CONNECTION,
"The device '%s' has no connections available for activation.",
nm_device_get_iface(self));
}
return sett_conn;
}
static void
cp_connection_added_or_updated(NMDevice *self, NMSettingsConnection *sett_conn)
{
gboolean changed;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IS_SETTINGS_CONNECTION(sett_conn));
if (nm_device_check_connection_available(self,
nm_settings_connection_get_connection(sett_conn),
_NM_DEVICE_CHECK_CON_AVAILABLE_FOR_USER_REQUEST,
NULL,
NULL))
changed = available_connections_add(self, sett_conn);
else
changed = available_connections_del(self, sett_conn);
if (changed) {
_notify(self, PROP_AVAILABLE_CONNECTIONS);
available_connections_check_delete_unrealized(self);
}
}
static void
cp_connection_added(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
{
cp_connection_added_or_updated(user_data, sett_conn);
}
static void
cp_connection_updated(NMSettings *settings,
NMSettingsConnection *sett_conn,
guint update_reason_u,
gpointer user_data)
{
cp_connection_added_or_updated(user_data, sett_conn);
}
static void
cp_connection_removed(NMSettings *settings, NMSettingsConnection *sett_conn, gpointer user_data)
{
NMDevice *self = user_data;
g_return_if_fail(NM_IS_DEVICE(self));
if (available_connections_del(self, sett_conn)) {
_notify(self, PROP_AVAILABLE_CONNECTIONS);
available_connections_check_delete_unrealized(self);
}
}
gboolean
nm_device_supports_vlans(NMDevice *self)
{
return nm_platform_link_supports_vlans(nm_device_get_platform(self),
nm_device_get_ifindex(self));
}
/**
* nm_device_add_pending_action():
* @self: the #NMDevice to add the pending action to
* @action: a static string that identifies the action. The string instance must
* stay valid until the pending action is removed (that is, the string is
* not cloned, but ownership stays with the caller).
* @assert_not_yet_pending: if %TRUE, assert that the @action is currently not yet pending.
* Otherwise, ignore duplicate scheduling of the same action silently.
*
* Adds a pending action to the device.
*
* Returns: %TRUE if the action was added (and not already added before). %FALSE
* if the same action is already scheduled. In the latter case, the action was not scheduled
* a second time.
*/
gboolean
nm_device_add_pending_action(NMDevice *self, const char *action, gboolean assert_not_yet_pending)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gssize idx;
g_return_val_if_fail(action, FALSE);
idx = nm_strv_find_binary_search(priv->pending_actions.arr, priv->pending_actions.len, action);
if (idx >= 0) {
if (assert_not_yet_pending) {
_LOGW(LOGD_DEVICE,
"add_pending_action (%u): '%s' already pending",
priv->pending_actions.len,
action);
g_return_val_if_reached(FALSE);
} else {
_LOGT(LOGD_DEVICE,
"add_pending_action (%u): '%s' already pending (expected)",
priv->pending_actions.len,
action);
}
return FALSE;
}
if (priv->pending_actions.len == priv->pending_actions.alloc) {
nm_assert(priv->pending_actions.alloc < G_MAXUINT / 2u);
priv->pending_actions.alloc = NM_MAX(priv->pending_actions.alloc * 2u, 4u);
priv->pending_actions.arr =
g_renew(const char *, priv->pending_actions.arr, priv->pending_actions.alloc);
}
nm_arr_insert_at(priv->pending_actions.arr, priv->pending_actions.len, ~idx, action);
priv->pending_actions.len++;
_LOGD(LOGD_DEVICE, "add_pending_action (%u): '%s'", priv->pending_actions.len, action);
if (priv->pending_actions.len == 1)
_notify(self, PROP_HAS_PENDING_ACTION);
return TRUE;
}
/**
* nm_device_remove_pending_action():
* @self: the #NMDevice to remove the pending action from
* @action: a string that identifies the action.
* @assert_is_pending: if %TRUE, assert that the @action is pending.
* If %FALSE, don't do anything if the current action is not pending and
* return %FALSE.
*
* Removes a pending action previously added by nm_device_add_pending_action().
*
* Returns: whether the @action was pending and is now removed.
*/
gboolean
nm_device_remove_pending_action(NMDevice *self, const char *action, gboolean assert_is_pending)
{
NMDevicePrivate *priv;
gssize idx;
g_return_val_if_fail(self, FALSE);
g_return_val_if_fail(action, FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
idx = nm_strv_find_binary_search(priv->pending_actions.arr, priv->pending_actions.len, action);
if (idx >= 0) {
_LOGD(LOGD_DEVICE,
"remove_pending_action (%u): '%s'",
priv->pending_actions.len - 1u,
action);
nm_arr_remove_at(priv->pending_actions.arr, priv->pending_actions.len, idx);
priv->pending_actions.len--;
if (priv->pending_actions.len == 0)
_notify(self, PROP_HAS_PENDING_ACTION);
return TRUE;
}
if (assert_is_pending) {
_LOGW(LOGD_DEVICE,
"remove_pending_action (%u): '%s' not pending",
priv->pending_actions.len,
action);
g_return_val_if_reached(FALSE);
} else {
_LOGT(LOGD_DEVICE,
"remove_pending_action (%u): '%s' not pending (expected)",
priv->pending_actions.len,
action);
}
return FALSE;
}
const char *
nm_device_has_pending_action_reason(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->pending_actions.len > 0) {
if (priv->pending_actions.len == 1 && nm_device_get_state(self) == NM_DEVICE_STATE_ACTIVATED
&& nm_streq(priv->pending_actions.arr[0], NM_PENDING_ACTION_CARRIER_WAIT)) {
/* if the device is already in activated state, and the only reason
* why it appears still busy is "carrier-wait", then we are already complete. */
return NULL;
}
return priv->pending_actions.arr[0];
}
if (nm_device_is_real(self)
&& nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
/* as long as the platform link is not yet initialized, we have a pending
* action. */
return NM_PENDING_ACTION_LINK_INIT;
}
return NULL;
}
/*****************************************************************************/
static void
_cancel_activation(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->fw_call) {
nm_firewalld_manager_cancel_call(priv->fw_call);
nm_assert(!priv->fw_call);
priv->fw_call = NULL;
priv->fw_state = FIREWALL_STATE_INITIALIZED;
}
_dispatcher_cleanup(self);
ip_check_gw_ping_cleanup(self);
/* Break the activation chain */
activation_source_clear(self);
}
static void
_cleanup_generic_pre(NMDevice *self, CleanupType cleanup_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint i;
_cancel_activation(self);
priv->stage1_sriov_state = NM_DEVICE_STAGE_STATE_INIT;
if (cleanup_type != CLEANUP_TYPE_KEEP) {
nm_manager_device_route_metric_clear(NM_MANAGER_GET, nm_device_get_ip_ifindex(self));
}
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE && priv->fw_state >= FIREWALL_STATE_INITIALIZED
&& priv->fw_mgr && !nm_device_sys_iface_state_is_external(self)) {
nm_firewalld_manager_remove_from_zone(priv->fw_mgr,
nm_device_get_ip_iface(self),
NULL,
NULL,
NULL);
}
priv->fw_state = FIREWALL_STATE_UNMANAGED;
g_clear_object(&priv->fw_mgr);
queued_state_clear(self);
for (i = 0; i < 2; i++)
nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
_cleanup_ip_pre(self, AF_INET, cleanup_type, FALSE);
_cleanup_ip_pre(self, AF_INET6, cleanup_type, FALSE);
_dev_ip_state_req_timeout_cancel(self, AF_UNSPEC);
}
static void
_cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = FALSE;
priv->l3config_merge_flags_has = FALSE;
priv->v4_route_table_all_sync_before = FALSE;
priv->v6_route_table_all_sync_before = FALSE;
priv->mtu_force_set_done = FALSE;
priv->needs_ip6_subnet = FALSE;
if (priv->act_request.obj) {
nm_active_connection_set_default(NM_ACTIVE_CONNECTION(priv->act_request.obj),
AF_INET,
FALSE);
nm_clear_g_signal_handler(priv->act_request.obj, &priv->master_ready_id);
act_request_set(self, NULL);
}
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
/* Check if the device was deactivated, and if so, delete_link.
* Don't call delete_link synchronously because we are currently
* handling a state change -- which is not reentrant. */
delete_on_deactivate_check_and_schedule(self);
}
/* ip_iface should be cleared after flushing all routes and addresses, since
* those are identified by ip_iface, not by iface (which might be a tty
* or ATM device).
*/
_set_ip_ifindex(self, 0, NULL);
nm_clear_g_source_inst(&priv->ip_data_4.check_async_source);
nm_clear_g_source_inst(&priv->ip_data_6.check_async_source);
}
/*
* nm_device_cleanup
*
* Remove a device's routing table entries and IP addresses.
*
*/
static void
nm_device_cleanup(NMDevice *self, NMDeviceStateReason reason, CleanupType cleanup_type)
{
NMDevicePrivate *priv;
int ifindex;
g_return_if_fail(NM_IS_DEVICE(self));
if (reason == NM_DEVICE_STATE_REASON_NOW_MANAGED)
_LOGD(LOGD_DEVICE, "preparing device");
else
_LOGD(LOGD_DEVICE,
"deactivating device (reason '%s') [%d]",
nm_device_state_reason_to_string_a(reason),
reason);
/* Save whether or not we tried IPv6 for later */
priv = NM_DEVICE_GET_PRIVATE(self);
_cleanup_generic_pre(self, cleanup_type);
/* Turn off kernel IPv6 */
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
_dev_sysctl_set_disable_ipv6(self, TRUE);
nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
}
/* Call device type-specific deactivation */
if (NM_DEVICE_GET_CLASS(self)->deactivate)
NM_DEVICE_GET_CLASS(self)->deactivate(self);
ifindex = nm_device_get_ip_ifindex(self);
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
/* master: release slaves */
nm_device_master_release_slaves(self);
/* Take out any entries in the routing table and any IP address the device had. */
if (ifindex > 0) {
NMPlatform *platform = nm_device_get_platform(self);
nm_device_l3cfg_commit(self, NM_L3_CFG_COMMIT_TYPE_REAPPLY, TRUE);
if (nm_device_get_applied_setting(self, NM_TYPE_SETTING_TC_CONFIG)) {
nm_platform_tc_sync(platform, ifindex, NULL, NULL);
}
}
}
priv->tc_committed = FALSE;
_routing_rules_sync(self,
cleanup_type == CLEANUP_TYPE_KEEP ? NM_TERNARY_DEFAULT : NM_TERNARY_FALSE);
if (ifindex > 0)
nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self), ifindex, NULL);
/* slave: mark no longer enslaved */
if (priv->master && priv->ifindex > 0
&& nm_platform_link_get_master(nm_device_get_platform(self), priv->ifindex) <= 0)
nm_device_master_release_one_slave(priv->master,
self,
FALSE,
FALSE,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
lldp_setup(self, NM_TERNARY_FALSE);
nm_device_update_metered(self);
if (ifindex > 0) {
/* during device cleanup, we want to reset the MAC address of the device
* to the initial state.
*
* We certainly want to do that when reaching the UNMANAGED state... */
if (nm_device_get_state(self) <= NM_DEVICE_STATE_UNMANAGED)
nm_device_hw_addr_reset(self, "unmanage");
else {
/* for other device states (UNAVAILABLE, DISCONNECTED), allow the
* device to overwrite the reset behavior, so that Wi-Fi can set
* a randomized MAC address used during scanning. */
NM_DEVICE_GET_CLASS(self)->deactivate_reset_hw_addr(self);
}
}
priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
priv->ip6_mtu = 0;
if (priv->mtu_initial || priv->ip6_mtu_initial) {
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex > 0 && cleanup_type == CLEANUP_TYPE_DECONFIGURE) {
_LOGT(LOGD_DEVICE,
"mtu: reset device-mtu: %u, ipv6-mtu: %u, ifindex: %d",
(guint) priv->mtu_initial,
(guint) priv->ip6_mtu_initial,
ifindex);
if (priv->mtu_initial) {
nm_platform_link_set_mtu(nm_device_get_platform(self), ifindex, priv->mtu_initial);
priv->carrier_wait_until_ms =
nm_utils_get_monotonic_timestamp_msec() + CARRIER_WAIT_TIME_AFTER_MTU_MS;
}
if (priv->ip6_mtu_initial) {
char sbuf[64];
nm_device_sysctl_ip_conf_set(
self,
AF_INET6,
"mtu",
nm_sprintf_buf(sbuf, "%u", (unsigned) priv->ip6_mtu_initial));
}
}
priv->mtu_initial = 0;
priv->ip6_mtu_initial = 0;
}
_ethtool_state_reset(self);
if (priv->promisc_reset != NM_OPTION_BOOL_DEFAULT && ifindex > 0) {
nm_platform_link_change_flags(nm_device_get_platform(self),
ifindex,
IFF_PROMISC,
!!priv->promisc_reset);
priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
}
_cleanup_generic_post(self, cleanup_type);
}
static void
deactivate_reset_hw_addr(NMDevice *self)
{
nm_device_hw_addr_reset(self, "deactivate");
}
/*****************************************************************************/
static void
ip6_managed_setup(NMDevice *self)
{
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
_dev_sysctl_set_disable_ipv6(self, FALSE);
nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", "0");
nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "0");
}
static void
deactivate_ready(NMDevice *self, NMDeviceStateReason reason)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->dispatcher.call_id)
return;
if (priv->sriov_reset_pending > 0)
return;
if (priv->state == NM_DEVICE_STATE_DEACTIVATING)
nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, reason);
}
static void
sriov_reset_on_deactivate_cb(GError *error, gpointer user_data)
{
NMDevice *self;
NMDevicePrivate *priv;
gpointer reason;
nm_utils_user_data_unpack(user_data, &self, &reason);
priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(priv->sriov_reset_pending > 0);
priv->sriov_reset_pending--;
if (nm_utils_error_is_cancelled(error))
return;
deactivate_ready(self, GPOINTER_TO_INT(reason));
}
static void
sriov_reset_on_failure_cb(GError *error, gpointer user_data)
{
NMDevice *self = user_data;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(priv->sriov_reset_pending > 0);
priv->sriov_reset_pending--;
if (nm_utils_error_is_cancelled(error))
return;
if (priv->state == NM_DEVICE_STATE_FAILED) {
nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
}
}
static void
deactivate_async_ready(NMDevice *self, GError *error, gpointer user_data)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceStateReason reason = GPOINTER_TO_UINT(user_data);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED)) {
_LOGD(LOGD_DEVICE, "Deactivation cancelled");
return;
}
g_clear_object(&priv->deactivating_cancellable);
/* In every other case, transition to the DISCONNECTED state */
if (error) {
_LOGW(LOGD_DEVICE, "Deactivation failed: %s", error->message);
}
deactivate_ready(self, reason);
}
static void
deactivate_dispatcher_complete(NMDispatcherCallId *call_id, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceStateReason reason;
g_return_if_fail(call_id == priv->dispatcher.call_id);
g_return_if_fail(priv->dispatcher.post_state == NM_DEVICE_STATE_DISCONNECTED);
reason = priv->state_reason;
priv->dispatcher.call_id = NULL;
priv->dispatcher.post_state = NM_DEVICE_STATE_UNKNOWN;
priv->dispatcher.post_state_reason = NM_DEVICE_STATE_REASON_NONE;
if (nm_clear_g_cancellable(&priv->deactivating_cancellable))
nm_assert_not_reached();
if (NM_DEVICE_GET_CLASS(self)->deactivate_async) {
/* FIXME: the virtual function deactivate_async() has only this caller here.
* And the NMDevice subtypes are well aware of the circumstances when they
* are called. We shall make the function less generic and thus (as the scope
* is narrower) more convenient.
*
* - Drop the callback argument. Instead, when deactivate_async() completes, the
* subtype shall call a method _nm_device_deactivate_async_done(). Because as
* it is currently, subtypes need to pretend this callback and the user-data
* would be opaque, and carry it around. When it's in fact very clear what this
* is.
*
* - Also drop the GCancellable argument. Upon cancellation, NMDevice shall
* call another virtual function deactivate_async_abort(). As it is currently,
* callers need to register to the cancelled signal of the cancellable. It
* seems simpler to just implement the deactivate_async_abort() function.
* On the other hand, some implementations actually use the GCancellable.
* So, NMDevice shall do both: it shall both pass a cancellable, but also
* invoke deactivate_async_abort(). It allow the implementation to honor
* whatever is simpler for their purpose.
*
* - sometimes, the subclass can complete right away. Scheduling the completion
* in an idle handler is cumbersome. Allow the function to return FALSE to
* indicate that the device is already deactivated and the callback (or
* _nm_device_deactivate_async_done()) won't be invoked.
*/
priv->deactivating_cancellable = g_cancellable_new();
NM_DEVICE_GET_CLASS(self)->deactivate_async(self,
priv->deactivating_cancellable,
deactivate_async_ready,
GUINT_TO_POINTER(reason));
} else
deactivate_ready(self, reason);
}
static void
_set_state_full(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason, gboolean quitting)
{
gs_unref_object NMActRequest *req = NULL;
NMDevicePrivate *priv;
NMDeviceState old_state;
gboolean no_firmware = FALSE;
NMSettingsConnection *sett_conn;
NMSettingSriov *s_sriov;
gboolean concheck_now;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_if_fail(priv->in_state_changed == 0);
old_state = priv->state;
if (state == NM_DEVICE_STATE_FAILED && nm_device_sys_iface_state_is_external_or_assume(self)) {
/* Avoid tearing down assumed connection, assume it's connected */
state = NM_DEVICE_STATE_ACTIVATED;
reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
}
/* Do nothing if state isn't changing, but as a special case allow
* re-setting UNAVAILABLE if the device is missing firmware so that we
* can retry device initialization.
*/
if ((priv->state == state)
&& (state != NM_DEVICE_STATE_UNAVAILABLE || !priv->firmware_missing)) {
_LOGD(LOGD_DEVICE,
"state change: %s -> %s (reason '%s', sys-iface-state: '%s'%s)",
nm_device_state_to_string(old_state),
nm_device_state_to_string(state),
nm_device_state_reason_to_string_a(reason),
nm_device_sys_iface_state_to_string(priv->sys_iface_state),
priv->firmware_missing ? ", missing firmware" : "");
return;
}
_LOGI(LOGD_DEVICE,
"state change: %s -> %s (reason '%s', sys-iface-state: '%s')",
nm_device_state_to_string(old_state),
nm_device_state_to_string(state),
nm_device_state_reason_to_string_a(reason),
nm_device_sys_iface_state_to_string(priv->sys_iface_state));
/* in order to prevent triggering any callback caused
* by the device not having any pending action anymore
* we add one here that gets removed at the end of the function */
nm_device_add_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE);
priv->in_state_changed++;
priv->state = state;
priv->state_reason = reason;
queued_state_clear(self);
_dispatcher_cleanup(self);
nm_clear_g_cancellable(&priv->deactivating_cancellable);
/* Cache the activation request for the dispatcher */
req = nm_g_object_ref(priv->act_request.obj);
if (state > NM_DEVICE_STATE_UNMANAGED && state <= NM_DEVICE_STATE_ACTIVATED
&& nm_device_state_reason_check(reason) == NM_DEVICE_STATE_REASON_NOW_MANAGED
&& NM_IN_SET_TYPED(NMDeviceSysIfaceState,
priv->sys_iface_state,
NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
NM_DEVICE_SYS_IFACE_STATE_ASSUME))
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_MANAGED);
if (state <= NM_DEVICE_STATE_DISCONNECTED || state >= NM_DEVICE_STATE_ACTIVATED)
priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
if (state > NM_DEVICE_STATE_DISCONNECTED)
nm_device_assume_state_reset(self);
if (state < NM_DEVICE_STATE_UNAVAILABLE
|| (state >= NM_DEVICE_STATE_IP_CONFIG && state < NM_DEVICE_STATE_ACTIVATED)) {
/* preserve-external-ports is used by NMCheckpoint to activate a master
* device, and preserve already attached ports. This means, this state is only
* relevant during the deactivation and the following activation of the
* right profile. Once we are sufficiently far in the activation of the
* intended profile, we clear the state again. */
nm_device_activation_state_set_preserve_external_ports(self, FALSE);
}
if (state <= NM_DEVICE_STATE_UNAVAILABLE) {
if (available_connections_del_all(self))
_notify(self, PROP_AVAILABLE_CONNECTIONS);
if (old_state > NM_DEVICE_STATE_UNAVAILABLE) {
_clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
}
}
/* Update the available connections list when a device first becomes available */
if (state >= NM_DEVICE_STATE_DISCONNECTED && old_state < NM_DEVICE_STATE_DISCONNECTED)
nm_device_recheck_available_connections(self);
if (state <= NM_DEVICE_STATE_DISCONNECTED || state > NM_DEVICE_STATE_DEACTIVATING) {
if (nm_clear_g_free(&priv->current_stable_id))
_LOGT(LOGD_DEVICE, "stable-id: clear");
}
/* Handle the new state here; but anything that could trigger
* another state change should be done below.
*/
switch (state) {
case NM_DEVICE_STATE_UNMANAGED:
nm_device_set_firmware_missing(self, FALSE);
if (old_state > NM_DEVICE_STATE_UNMANAGED) {
if (priv->sys_iface_state != NM_DEVICE_SYS_IFACE_STATE_MANAGED) {
nm_device_cleanup(self,
reason,
priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_REMOVED
? CLEANUP_TYPE_REMOVED
: CLEANUP_TYPE_KEEP);
} else {
/* Clean up if the device is now unmanaged but was activated */
if (nm_device_get_act_request(self))
nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
nm_device_take_down(self, TRUE);
nm_device_hw_addr_reset(self, "unmanage");
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
_dev_sysctl_restore_ip6_properties(self);
}
}
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_EXTERNAL);
break;
case NM_DEVICE_STATE_UNAVAILABLE:
if (old_state == NM_DEVICE_STATE_UNMANAGED) {
_dev_sysctl_save_ip6_properties(self);
if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED)
ip6_managed_setup(self);
device_init_static_sriov_num_vfs(self);
}
if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) {
if (old_state == NM_DEVICE_STATE_UNMANAGED || priv->firmware_missing) {
if (!nm_device_bring_up(self, TRUE, &no_firmware) && no_firmware)
_LOGW(LOGD_PLATFORM, "firmware may be missing.");
nm_device_set_firmware_missing(self, no_firmware ? TRUE : FALSE);
}
/* Ensure the device gets deactivated in response to stuff like
* carrier changes or rfkill. But don't deactivate devices that are
* about to assume a connection since that defeats the purpose of
* assuming the device's existing connection.
*
* Note that we "deactivate" the device even when coming from
* UNMANAGED, to ensure that it's in a clean state.
*/
nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
}
break;
case NM_DEVICE_STATE_DISCONNECTED:
if (old_state > NM_DEVICE_STATE_DISCONNECTED) {
/* Ensure devices that previously assumed a connection now have
* userspace IPv6LL enabled.
*/
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_NONE);
nm_device_cleanup(self, reason, CLEANUP_TYPE_DECONFIGURE);
} else if (old_state < NM_DEVICE_STATE_DISCONNECTED) {
if (priv->sys_iface_state == NM_DEVICE_SYS_IFACE_STATE_MANAGED) {
/* Ensure IPv6 is set up as it may not have been done when
* entering the UNAVAILABLE state depending on the reason.
*/
ip6_managed_setup(self);
}
}
break;
case NM_DEVICE_STATE_PREPARE:
nm_device_update_initial_hw_address(self);
break;
case NM_DEVICE_STATE_NEED_AUTH:
if (old_state > NM_DEVICE_STATE_NEED_AUTH) {
/* Clean up any half-done IP operations if the device's layer2
* finds out it needs authentication during IP config.
*/
_cleanup_ip_pre(self, AF_INET, CLEANUP_TYPE_DECONFIGURE, FALSE);
_cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE, FALSE);
}
break;
default:
break;
}
/* Reset intern autoconnect flags when the device is activating or connected. */
if (state >= NM_DEVICE_STATE_PREPARE && state <= NM_DEVICE_STATE_ACTIVATED)
nm_device_autoconnect_blocked_unset(self, NM_DEVICE_AUTOCONNECT_BLOCKED_INTERNAL);
_notify(self, PROP_STATE);
_notify(self, PROP_STATE_REASON);
nm_dbus_object_emit_signal(NM_DBUS_OBJECT(self),
&interface_info_device,
&signal_info_state_changed,
"(uuu)",
(guint32) state,
(guint32) old_state,
(guint32) reason);
g_signal_emit(self,
signals[STATE_CHANGED],
0,
(guint) state,
(guint) old_state,
(guint) reason);
/* Post-process the event after internal notification */
switch (state) {
case NM_DEVICE_STATE_UNAVAILABLE:
/* If the device can activate now (ie, it's got a carrier, the supplicant
* is active, or whatever) schedule a delayed transition to DISCONNECTED
* to get things rolling. The device can't transition immediately because
* we can't change states again from the state handler for a variety of
* reasons.
*/
if (nm_device_is_available(self, NM_DEVICE_CHECK_DEV_AVAILABLE_NONE)) {
nm_device_queue_recheck_available(self,
NM_DEVICE_STATE_REASON_NONE,
NM_DEVICE_STATE_REASON_NONE);
} else {
_LOGD(LOGD_DEVICE, "device not yet available for transition to DISCONNECTED");
}
break;
case NM_DEVICE_STATE_DEACTIVATING:
_cancel_activation(self);
/* We cache the ignore_carrier state to not react on config-reloads while the connection
* is active. But on deactivating, reset the ignore-carrier flag to the current state. */
priv->ignore_carrier = nm_config_data_get_ignore_carrier(NM_CONFIG_GET_DATA, self);
if (quitting) {
nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_PRE_DOWN, self, req);
} else {
priv->dispatcher.post_state = NM_DEVICE_STATE_DISCONNECTED;
priv->dispatcher.post_state_reason = reason;
if (!nm_dispatcher_call_device(NM_DISPATCHER_ACTION_PRE_DOWN,
self,
req,
deactivate_dispatcher_complete,
self,
&priv->dispatcher.call_id)) {
/* Just proceed on errors */
deactivate_dispatcher_complete(0, self);
}
if (priv->ifindex > 0
&& (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
priv->sriov_reset_pending++;
sriov_op_queue(self,
0,
NM_OPTION_BOOL_TRUE,
sriov_reset_on_deactivate_cb,
nm_utils_user_data_pack(self, GINT_TO_POINTER(reason)));
}
}
nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id);
break;
case NM_DEVICE_STATE_DISCONNECTED:
if (priv->queued_act_request && !priv->queued_act_request_is_waiting_for_carrier) {
gs_unref_object NMActRequest *queued_req = NULL;
queued_req = g_steal_pointer(&priv->queued_act_request);
_device_activate(self, queued_req);
}
break;
case NM_DEVICE_STATE_ACTIVATED:
_LOGI(LOGD_DEVICE, "Activation: successful, device activated.");
nm_device_update_metered(self);
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_UP, self, req, NULL, NULL, NULL);
_pacrunner_manager_add(self);
break;
case NM_DEVICE_STATE_FAILED:
/* Usually upon failure the activation chain is interrupted in
* one of the stages; but in some cases the device fails for
* external events (as a failure of master connection) while
* the activation sequence is running and so we need to ensure
* that the chain is terminated here.
*/
_cancel_activation(self);
sett_conn = nm_device_get_settings_connection(self);
_LOGW(LOGD_DEVICE | LOGD_WIFI,
"Activation: failed for connection '%s'",
sett_conn ? nm_settings_connection_get_id(sett_conn) : "<unknown>");
/* Notify any slaves of the unexpected failure */
nm_device_master_release_slaves(self);
/* If the connection doesn't yet have a timestamp, set it to zero so that
* we can distinguish between connections we've tried to activate and have
* failed (zero timestamp), connections that succeeded (non-zero timestamp),
* and those we haven't tried yet (no timestamp).
*/
if (sett_conn && !nm_settings_connection_get_timestamp(sett_conn, NULL))
nm_settings_connection_update_timestamp(sett_conn, (guint64) 0);
if (priv->ifindex > 0
&& (s_sriov = nm_device_get_applied_setting(self, NM_TYPE_SETTING_SRIOV))) {
priv->sriov_reset_pending++;
sriov_op_queue(self, 0, NM_OPTION_BOOL_TRUE, sriov_reset_on_failure_cb, self);
break;
}
/* Schedule the transition to DISCONNECTED. The device can't transition
* immediately because we can't change states again from the state
* handler for a variety of reasons.
*/
nm_device_queue_state(self, NM_DEVICE_STATE_DISCONNECTED, NM_DEVICE_STATE_REASON_NONE);
break;
case NM_DEVICE_STATE_IP_CHECK:
{
gboolean change_zone = FALSE;
if (!nm_device_sys_iface_state_is_external(self)) {
if (priv->ip_iface) {
/* The device now has a @ip_iface different from the
* @iface on which we previously set the zone. */
change_zone = TRUE;
} else if (priv->fw_state == FIREWALL_STATE_UNMANAGED && priv->ifindex > 0) {
/* We didn't set the zone earlier because there was
* no ifindex. */
change_zone = TRUE;
}
}
if (change_zone) {
priv->fw_state = FIREWALL_STATE_WAIT_IP_CONFIG;
fw_change_zone(self);
} else
nm_device_start_ip_check(self);
break;
}
case NM_DEVICE_STATE_SECONDARIES:
ip_check_gw_ping_cleanup(self);
_LOGD(LOGD_DEVICE, "device entered SECONDARIES state");
break;
default:
break;
}
if (state > NM_DEVICE_STATE_DISCONNECTED)
delete_on_deactivate_unschedule(self);
if ((old_state == NM_DEVICE_STATE_ACTIVATED || old_state == NM_DEVICE_STATE_DEACTIVATING)
&& (state != NM_DEVICE_STATE_DEACTIVATING)) {
if (quitting) {
nm_dispatcher_call_device_sync(NM_DISPATCHER_ACTION_DOWN, self, req);
} else {
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DOWN, self, req, NULL, NULL, NULL);
}
}
concheck_now = NM_IN_SET(state, NM_DEVICE_STATE_ACTIVATED, NM_DEVICE_STATE_DISCONNECTED)
|| old_state >= NM_DEVICE_STATE_ACTIVATED;
concheck_update_interval(self, AF_INET, concheck_now);
concheck_update_interval(self, AF_INET6, concheck_now);
update_prop_ip_iface(self);
priv->in_state_changed--;
nm_device_remove_pending_action(self, NM_PENDING_ACTION_IN_STATE_CHANGE, TRUE);
if ((old_state > NM_DEVICE_STATE_UNMANAGED) != (state > NM_DEVICE_STATE_UNMANAGED))
_notify(self, PROP_MANAGED);
}
void
nm_device_state_changed(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason)
{
_set_state_full(self, state, reason, FALSE);
}
static gboolean
queued_state_set(gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceState new_state;
NMDeviceStateReason new_reason;
nm_assert(priv->queued_state.id);
_LOGD(LOGD_DEVICE,
"queue-state[%s, reason:%s, id:%u]: %s",
nm_device_state_to_string(priv->queued_state.state),
nm_device_state_reason_to_string_a(priv->queued_state.reason),
priv->queued_state.id,
"change state");
/* Clear queued state struct before triggering state change, since
* the state change may queue another state.
*/
priv->queued_state.id = 0;
new_state = priv->queued_state.state;
new_reason = priv->queued_state.reason;
nm_device_state_changed(self, new_state, new_reason);
nm_device_remove_pending_action(self, nm_device_state_queued_state_to_string(new_state), TRUE);
return G_SOURCE_REMOVE;
}
void
nm_device_queue_state(NMDevice *self, NMDeviceState state, NMDeviceStateReason reason)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->queued_state.id && priv->queued_state.state == state) {
_LOGD(LOGD_DEVICE,
"queue-state[%s, reason:%s, id:%u]: %s%s%s%s",
nm_device_state_to_string(priv->queued_state.state),
nm_device_state_reason_to_string_a(priv->queued_state.reason),
priv->queued_state.id,
"ignore queuing same state change",
NM_PRINT_FMT_QUOTED(priv->queued_state.reason != reason,
" (reason differs: ",
nm_device_state_reason_to_string_a(reason),
")",
""));
return;
}
/* Add pending action for the new state before clearing the queued states, so
* that we don't accidentally pop all pending states and reach 'startup complete' */
nm_device_add_pending_action(self, nm_device_state_queued_state_to_string(state), TRUE);
/* We should only ever have one delayed state transition at a time */
if (priv->queued_state.id) {
_LOGW(LOGD_DEVICE,
"queue-state[%s, reason:%s, id:%u]: %s",
nm_device_state_to_string(priv->queued_state.state),
nm_device_state_reason_to_string_a(priv->queued_state.reason),
priv->queued_state.id,
"replace previously queued state change");
nm_clear_g_source(&priv->queued_state.id);
nm_device_remove_pending_action(
self,
nm_device_state_queued_state_to_string(priv->queued_state.state),
TRUE);
}
priv->queued_state.state = state;
priv->queued_state.reason = reason;
priv->queued_state.id = g_idle_add(queued_state_set, self);
_LOGD(LOGD_DEVICE,
"queue-state[%s, reason:%s, id:%u]: %s",
nm_device_state_to_string(state),
nm_device_state_reason_to_string_a(reason),
priv->queued_state.id,
"queue state change");
}
static void
queued_state_clear(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->queued_state.id)
return;
_LOGD(LOGD_DEVICE,
"queue-state[%s, reason:%s, id:%u]: %s",
nm_device_state_to_string(priv->queued_state.state),
nm_device_state_reason_to_string_a(priv->queued_state.reason),
priv->queued_state.id,
"clear queued state change");
nm_clear_g_source(&priv->queued_state.id);
nm_device_remove_pending_action(
self,
nm_device_state_queued_state_to_string(priv->queued_state.state),
TRUE);
}
NMDeviceState
nm_device_get_state(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_STATE_UNKNOWN);
return NM_DEVICE_GET_PRIVATE(self)->state;
}
/*****************************************************************************/
/**
* nm_device_activation_state_set_preserve_external_ports:
* @self: the NMDevice.
* @flag: whether to set or clear the the flag.
*
* This sets an internal flag to true, which does something specific.
* For non-master devices, it has no effect. For master devices, this
* will prevent to detach all external ports, until the next activation
* completes.
*
* This is used during checkpoint/rollback. We may want to preserve
* externally attached ports during the restore. NMCheckpoint will
* call this before doing a re-activation. By setting the flag,
* we basically preserve such ports.
*
* Once we reach again ACTIVATED state, the flag gets cleared. This
* only has effect for the next activation cycle. */
void
nm_device_activation_state_set_preserve_external_ports(NMDevice *self, gboolean flag)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (!NM_IS_DEVICE_BRIDGE(self)) {
/* This is actually only implemented for bridge devices. While it might
* make sense for bond/team or OVS, it's not clear that it is actually
* useful or desirable. */
return;
}
if (priv->activation_state_preserve_external_ports == flag)
return;
priv->activation_state_preserve_external_ports = flag;
_LOGD(LOGD_DEVICE,
"activation-state: preserve-external-ports %s",
flag ? "enabled" : "disabled");
}
/*****************************************************************************/
/* NMConfigDevice interface related stuff */
const char *
nm_device_get_hw_address(NMDevice *self)
{
NMDevicePrivate *priv;
char buf[_NM_UTILS_HWADDR_LEN_MAX];
gsize l;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert((!priv->hw_addr && priv->hw_addr_len == 0)
|| (priv->hw_addr && _nm_utils_hwaddr_aton(priv->hw_addr, buf, sizeof(buf), &l)
&& l == priv->hw_addr_len));
return priv->hw_addr;
}
gboolean
nm_device_update_hw_address(NMDevice *self)
{
NMDevicePrivate *priv;
const guint8 *hwaddr;
gsize hwaddrlen = 0;
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ifindex <= 0)
return FALSE;
hwaddr = nm_platform_link_get_address(nm_device_get_platform(self), priv->ifindex, &hwaddrlen);
if (priv->type == NM_DEVICE_TYPE_ETHERNET && hwaddr
&& nm_utils_hwaddr_matches(hwaddr,
hwaddrlen,
&nm_ether_addr_zero,
sizeof(nm_ether_addr_zero)))
hwaddrlen = 0;
if (!hwaddrlen)
return FALSE;
if (priv->hw_addr_len && priv->hw_addr_len != hwaddrlen) {
char s_buf[NM_UTILS_HWADDR_LEN_MAX_STR];
/* we cannot change the address length of a device once it is set (except
* unrealizing the device).
*
* The reason is that the permanent and initial MAC addresses also must have the
* same address length, so it's unclear what it would mean that the length changes. */
_LOGD(LOGD_PLATFORM | LOGD_DEVICE,
"hw-addr: read a MAC address with differing length (%s vs. %s)",
priv->hw_addr,
_nm_utils_hwaddr_ntoa(hwaddr, hwaddrlen, TRUE, s_buf, sizeof(s_buf)));
return FALSE;
}
if (priv->hw_addr && nm_utils_hwaddr_matches(priv->hw_addr, -1, hwaddr, hwaddrlen))
return FALSE;
g_free(priv->hw_addr);
priv->hw_addr_len_ = hwaddrlen;
priv->hw_addr = nm_utils_hwaddr_ntoa(hwaddr, hwaddrlen);
_LOGD(LOGD_PLATFORM | LOGD_DEVICE, "hw-addr: hardware address now %s", priv->hw_addr);
_notify(self, PROP_HW_ADDRESS);
if (!priv->hw_addr_initial
|| (priv->hw_addr_type == HW_ADDR_TYPE_UNSET && priv->state < NM_DEVICE_STATE_PREPARE
&& !nm_device_is_activating(self))) {
/* when we get a hw_addr the first time or while the device
* is not activated (with no explicit hw address set), always
* update our initial hw-address as well. */
nm_device_update_initial_hw_address(self);
}
return TRUE;
}
void
nm_device_update_initial_hw_address(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->hw_addr && !nm_streq0(priv->hw_addr_initial, priv->hw_addr)) {
if (priv->hw_addr_initial && priv->hw_addr_type != HW_ADDR_TYPE_UNSET) {
/* once we have the initial hw address set, we only allow
* update if the currently type is "unset". */
return;
}
g_free(priv->hw_addr_initial);
priv->hw_addr_initial = g_strdup(priv->hw_addr);
_LOGD(LOGD_DEVICE, "hw-addr: update initial MAC address %s", priv->hw_addr_initial);
}
}
void
nm_device_update_permanent_hw_address(NMDevice *self, gboolean force_freeze)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint8 buf[_NM_UTILS_HWADDR_LEN_MAX];
gboolean success_read;
int ifindex;
const NMPlatformLink *pllink;
const NMConfigDeviceStateData *dev_state;
NMPLinkAddress cached_hw_addr_perm;
if (priv->hw_addr_perm) {
/* the permanent hardware address is only read once and not
* re-read later.
*
* Except during unrealize/realize cycles, where we clear the permanent
* hardware address during unrealization. */
return;
}
ifindex = priv->ifindex;
if (ifindex <= 0)
return;
/* the user is advised to configure stable MAC addresses for software devices via
* UDEV. Thus, check whether the link is fully initialized. */
pllink = nm_platform_link_get(nm_device_get_platform(self), ifindex);
if (!pllink || !pllink->initialized) {
if (!force_freeze) {
/* we can afford to wait. Back off and leave the permanent MAC address
* undecided for now. */
return;
}
/* try to refresh the link just to give UDEV a bit more time... */
nm_platform_link_refresh(nm_device_get_platform(self), ifindex);
/* maybe the MAC address changed... */
nm_device_update_hw_address(self);
} else if (!priv->hw_addr_len)
nm_device_update_hw_address(self);
if (!priv->hw_addr_len) {
/* we need the current MAC address because we require the permanent MAC address
* to have the same length as the current address.
*
* Abort if there is no current MAC address. */
return;
}
success_read = nm_platform_link_get_permanent_address(nm_device_get_platform(self),
pllink,
&cached_hw_addr_perm);
if (success_read && priv->hw_addr_len == cached_hw_addr_perm.len) {
priv->hw_addr_perm_fake = FALSE;
priv->hw_addr_perm =
nm_utils_hwaddr_ntoa(cached_hw_addr_perm.data, cached_hw_addr_perm.len);
_LOGD(LOGD_DEVICE, "hw-addr: read permanent MAC address '%s'", priv->hw_addr_perm);
goto notify_and_out;
}
/* we failed to read a permanent MAC address, thus we use a fake address,
* that is the current MAC address of the device.
*
* Note that the permanet MAC address of a NMDevice instance does not change
* after being set once. Thus, we use now a fake address and stick to that
* (until we unrealize the device). */
priv->hw_addr_perm_fake = TRUE;
/* We also persist our choice of the fake address to the device state
* file to use the same address on restart of NetworkManager.
* First, try to reload the address from the state file. */
dev_state = nm_config_device_state_get(nm_config_get(), ifindex);
if (dev_state && dev_state->perm_hw_addr_fake
&& nm_utils_hwaddr_aton(dev_state->perm_hw_addr_fake, buf, priv->hw_addr_len)
&& !nm_utils_hwaddr_matches(buf, priv->hw_addr_len, priv->hw_addr, -1)) {
_LOGD(LOGD_PLATFORM | LOGD_ETHER,
"hw-addr: %s (use from statefile: %s, current: %s)",
success_read ? "read HW addr length of permanent MAC address differs"
: "unable to read permanent MAC address",
dev_state->perm_hw_addr_fake,
priv->hw_addr);
priv->hw_addr_perm = nm_utils_hwaddr_ntoa(buf, priv->hw_addr_len);
goto notify_and_out;
}
_LOGD(LOGD_PLATFORM | LOGD_ETHER,
"hw-addr: %s (use current: %s)",
success_read ? "read HW addr length of permanent MAC address differs"
: "unable to read permanent MAC address",
priv->hw_addr);
priv->hw_addr_perm = g_strdup(priv->hw_addr);
notify_and_out:
_notify(self, PROP_PERM_HW_ADDRESS);
}
gboolean
nm_device_hw_addr_is_explict(NMDevice *self)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
return !NM_IN_SET((HwAddrType) priv->hw_addr_type, HW_ADDR_TYPE_PERMANENT, HW_ADDR_TYPE_UNSET);
}
static gboolean
_hw_addr_matches(NMDevice *self, const guint8 *addr, gsize addr_len)
{
const char *cur_addr;
cur_addr = nm_device_get_hw_address(self);
return cur_addr && nm_utils_hwaddr_matches(addr, addr_len, cur_addr, -1);
}
static gboolean
_hw_addr_set(NMDevice *self,
const char *const addr,
const char *const operation,
const char *const detail)
{
NMDevicePrivate *priv;
gboolean success = FALSE;
int r;
guint8 addr_bytes[_NM_UTILS_HWADDR_LEN_MAX];
gsize addr_len;
gboolean was_taken_down = FALSE;
gboolean retry_down;
nm_assert(NM_IS_DEVICE(self));
nm_assert(addr);
nm_assert(operation);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!_nm_utils_hwaddr_aton(addr, addr_bytes, sizeof(addr_bytes), &addr_len))
g_return_val_if_reached(FALSE);
/* Do nothing if current MAC is same */
if (_hw_addr_matches(self, addr_bytes, addr_len)) {
_LOGT(LOGD_DEVICE, "set-hw-addr: no MAC address change needed (%s)", addr);
return TRUE;
}
if (priv->hw_addr_len && priv->hw_addr_len != addr_len) {
_LOGT(LOGD_DEVICE,
"set-hw-addr: setting MAC address to '%s' (%s, %s) failed because of wrong address "
"length (should be %u bytes)",
addr,
operation,
detail,
priv->hw_addr_len);
return FALSE;
}
_LOGT(LOGD_DEVICE,
"set-hw-addr: setting MAC address to '%s' (%s, %s)...",
addr,
operation,
detail);
if (nm_device_get_device_type(self) == NM_DEVICE_TYPE_WIFI) {
/* Always take the device down for Wi-Fi because
* wpa_supplicant needs it to properly detect the MAC
* change. */
retry_down = FALSE;
was_taken_down = TRUE;
nm_device_take_down(self, FALSE);
}
again:
r = nm_platform_link_set_address(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
addr_bytes,
addr_len);
success = (r >= 0);
if (!success) {
retry_down =
!was_taken_down && r != -NME_PL_NOT_FOUND
&& nm_platform_link_is_up(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
_NMLOG((retry_down || r == -NME_PL_NOT_FOUND) ? LOGL_DEBUG : LOGL_WARN,
LOGD_DEVICE,
"set-hw-addr: failed to %s MAC address to %s (%s) (%s)%s",
operation,
addr,
detail,
nm_strerror(r),
retry_down ? " (retry with taking down)" : "");
} else {
/* MAC address successfully changed; update the current MAC to match */
nm_device_update_hw_address(self);
if (!_hw_addr_matches(self, addr_bytes, addr_len)) {
gint64 poll_end, now;
_LOGD(LOGD_DEVICE,
"set-hw-addr: new MAC address %s not successfully %s (%s) (refresh link)",
addr,
operation,
detail);
/* The platform call indicated success, however the address is not
* as expected. That is either due to a driver issue (brcmfmac, bgo#770456,
* rh#1374023) or a race where externally the MAC address was reset.
* The race is rather unlikely.
*
* The alternative would be to postpone the activation in case the
* MAC address is not yet ready and poll without blocking. However,
* that is rather complicated and it is not expected that this case
* happens for regular drivers.
* Note that brcmfmac can block NetworkManager for 500 msec while
* taking down the device. Let's add another 100 msec to that.
*
* wait/poll up to 100 msec until it changes. */
poll_end = nm_utils_get_monotonic_timestamp_usec() + (100 * 1000);
for (;;) {
if (!nm_platform_link_refresh(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self)))
goto handle_fail;
if (!nm_device_update_hw_address(self))
goto handle_wait;
if (!_hw_addr_matches(self, addr_bytes, addr_len))
goto handle_fail;
break;
handle_wait:
now = nm_utils_get_monotonic_timestamp_usec();
if (now < poll_end) {
g_usleep(NM_MIN(poll_end - now, 500));
continue;
}
handle_fail:
success = FALSE;
break;
}
}
if (success) {
retry_down = FALSE;
_LOGI(LOGD_DEVICE, "set-hw-addr: %s MAC address to %s (%s)", operation, addr, detail);
} else {
retry_down = !was_taken_down
&& nm_platform_link_is_up(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self));
_NMLOG(retry_down ? LOGL_DEBUG : LOGL_WARN,
LOGD_DEVICE,
"set-hw-addr: new MAC address %s not successfully %s (%s)%s",
addr,
operation,
detail,
retry_down ? " (retry with taking down)" : "");
}
}
if (retry_down) {
/* changing the MAC address failed, but also the device was up (and we did not yet try to take
* it down). Optimally, we change the MAC address without taking the device down, but some
* devices don't like that. So, retry with taking the device down. */
retry_down = FALSE;
was_taken_down = TRUE;
nm_device_take_down(self, FALSE);
goto again;
}
if (was_taken_down) {
if (!nm_device_bring_up(self, TRUE, NULL))
return FALSE;
}
return success;
}
gboolean
nm_device_hw_addr_set(NMDevice *self, const char *addr, const char *detail, gboolean set_permanent)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!addr)
g_return_val_if_reached(FALSE);
if (set_permanent) {
/* The type is set to PERMANENT by NMDeviceVlan when taking the MAC
* address from the parent and by NMDeviceWifi when setting a random MAC
* address during scanning.
*/
priv->hw_addr_type = HW_ADDR_TYPE_PERMANENT;
}
return _hw_addr_set(self, addr, "set", detail);
}
/*
* _hw_addr_get_cloned:
* @self: a #NMDevice
* @connection: a #NMConnection
* @is_wifi: whether the device is Wi-Fi
* @preserve: (out): whether the address must be reset to initial one
* @hwaddr: (out): the cloned MAC address to set on interface
* @hwaddr_type: (out): the type of address to set
* @hwaddr_detail: (out): the detail (origin) of address to set
* @error: (out): on return, an error or %NULL
*
* Computes the MAC to be set on a interface. On success, one of the
* following exclusive conditions are verified:
*
* - @preserve is %TRUE: the address must be reset to the initial one
* - @hwaddr is not %NULL: the given address must be set on the device
* - @hwaddr is %NULL and @preserve is %FALSE: no action needed
*
* Returns: %FALSE in case of error in determining the cloned MAC address,
* %TRUE otherwise
*/
static gboolean
_hw_addr_get_cloned(NMDevice *self,
NMConnection *connection,
gboolean is_wifi,
gboolean *preserve,
char **hwaddr,
HwAddrType *hwaddr_type,
const char **hwaddr_detail,
GError **error)
{
NMDevicePrivate *priv;
gs_free char *hw_addr_generated = NULL;
const char *addr;
const char *addr_setting;
char *addr_out;
HwAddrType type_out;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
g_return_val_if_fail(NM_IS_CONNECTION(connection), FALSE);
g_return_val_if_fail(!error || !*error, FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!connection)
g_return_val_if_reached(FALSE);
addr_setting = _prop_get_x_cloned_mac_address(self, connection, is_wifi);
addr = addr_setting;
if (nm_streq(addr, NM_CLONED_MAC_PRESERVE)) {
/* "preserve" means to reset the initial MAC address. */
NM_SET_OUT(preserve, TRUE);
NM_SET_OUT(hwaddr, NULL);
NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET);
NM_SET_OUT(hwaddr_detail, addr_setting);
return TRUE;
}
if (nm_streq(addr, NM_CLONED_MAC_PERMANENT)) {
gboolean is_fake;
addr = nm_device_get_permanent_hw_address_full(self, TRUE, &is_fake);
if (is_fake) {
/* Preserve the current address if the permanent address if fake */
NM_SET_OUT(preserve, TRUE);
NM_SET_OUT(hwaddr, NULL);
NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET);
NM_SET_OUT(hwaddr_detail, addr_setting);
return TRUE;
} else if (!addr) {
g_set_error_literal(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"failed to retrieve permanent address");
return FALSE;
}
addr_out = g_strdup(addr);
type_out = HW_ADDR_TYPE_PERMANENT;
} else if (NM_IN_STRSET(addr, NM_CLONED_MAC_RANDOM)) {
if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
/* hm, we already use a generate MAC address. Most certainly, that is from the same
* activation request, so we should not create a new random address, instead keep
* the current. */
goto out_no_action;
}
hw_addr_generated = nm_utils_hw_addr_gen_random_eth(
nm_device_get_initial_hw_address(self),
_prop_get_x_generate_mac_address_mask(self, connection, is_wifi));
if (!hw_addr_generated) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"failed to generate %s MAC address",
"random");
return FALSE;
}
addr_out = g_steal_pointer(&hw_addr_generated);
type_out = HW_ADDR_TYPE_GENERATED;
} else if (NM_IN_STRSET(addr, NM_CLONED_MAC_STABLE)) {
NMUtilsStableType stable_type;
const char *stable_id;
if (priv->hw_addr_type == HW_ADDR_TYPE_GENERATED) {
/* hm, we already use a generate MAC address. Most certainly, that is from the same
* activation request, so let's skip creating the stable address anew. */
goto out_no_action;
}
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
hw_addr_generated = nm_utils_hw_addr_gen_stable_eth(
stable_type,
stable_id,
nm_device_get_ip_iface(self),
nm_device_get_initial_hw_address(self),
_prop_get_x_generate_mac_address_mask(self, connection, is_wifi));
if (!hw_addr_generated) {
g_set_error(error,
NM_DEVICE_ERROR,
NM_DEVICE_ERROR_FAILED,
"failed to generate %s MAC address",
"stable");
return FALSE;
}
addr_out = g_steal_pointer(&hw_addr_generated);
type_out = HW_ADDR_TYPE_GENERATED;
} else {
/* this must be a valid address. Otherwise, we shouldn't come here. */
if (!nm_utils_hwaddr_valid(addr, -1))
g_return_val_if_reached(FALSE);
addr_out = g_strdup(addr);
type_out = HW_ADDR_TYPE_EXPLICIT;
}
NM_SET_OUT(preserve, FALSE);
NM_SET_OUT(hwaddr, addr_out);
NM_SET_OUT(hwaddr_type, type_out);
NM_SET_OUT(hwaddr_detail, addr_setting);
return TRUE;
out_no_action:
NM_SET_OUT(preserve, FALSE);
NM_SET_OUT(hwaddr, NULL);
NM_SET_OUT(hwaddr_type, HW_ADDR_TYPE_UNSET);
NM_SET_OUT(hwaddr_detail, NULL);
return TRUE;
}
gboolean
nm_device_hw_addr_get_cloned(NMDevice *self,
NMConnection *connection,
gboolean is_wifi,
char **hwaddr,
gboolean *preserve,
GError **error)
{
if (!_hw_addr_get_cloned(self, connection, is_wifi, preserve, hwaddr, NULL, NULL, error))
return FALSE;
return TRUE;
}
gboolean
nm_device_hw_addr_set_cloned(NMDevice *self, NMConnection *connection, gboolean is_wifi)
{
NMDevicePrivate *priv;
gboolean preserve = FALSE;
gs_free char *hwaddr = NULL;
const char *detail = NULL;
HwAddrType type = HW_ADDR_TYPE_UNSET;
gs_free_error GError *error = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (!_hw_addr_get_cloned(self,
connection,
is_wifi,
&preserve,
&hwaddr,
&type,
&detail,
&error)) {
_LOGW(LOGD_DEVICE, "set-hw-addr: %s", error->message);
return FALSE;
}
if (preserve)
return nm_device_hw_addr_reset(self, detail);
if (hwaddr) {
priv->hw_addr_type = type;
return _hw_addr_set(self, hwaddr, "set-cloned", detail);
}
return TRUE;
}
gboolean
nm_device_hw_addr_reset(NMDevice *self, const char *detail)
{
NMDevicePrivate *priv;
const char *addr;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->hw_addr_type == HW_ADDR_TYPE_UNSET)
return TRUE;
priv->hw_addr_type = HW_ADDR_TYPE_UNSET;
addr = nm_device_get_initial_hw_address(self);
if (!addr) {
/* as hw_addr_type is not UNSET, we expect that we can get an
* initial address to which to reset. */
g_return_val_if_reached(FALSE);
}
return _hw_addr_set(self, addr, "reset", detail);
}
const char *
nm_device_get_permanent_hw_address_full(NMDevice *self,
gboolean force_freeze,
gboolean *out_is_fake)
{
NMDevicePrivate *priv;
g_return_val_if_fail(NM_IS_DEVICE(self), ({
NM_SET_OUT(out_is_fake, FALSE);
NULL;
}));
priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->hw_addr_perm && force_freeze) {
/* somebody requests a permanent MAC address, but we don't have it set
* yet. We cannot delay it any longer and try to get it without waiting
* for UDEV. */
nm_device_update_permanent_hw_address(self, TRUE);
}
NM_SET_OUT(out_is_fake, priv->hw_addr_perm && priv->hw_addr_perm_fake);
return priv->hw_addr_perm;
}
const char *
nm_device_get_permanent_hw_address(NMDevice *self)
{
return nm_device_get_permanent_hw_address_full(self, TRUE, NULL);
}
const char *
nm_device_get_initial_hw_address(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->hw_addr_initial;
}
/**
* nm_device_spec_match_list:
* @self: an #NMDevice
* @specs: (element-type utf8): a list of device specs
*
* Checks if @self matches any of the specifications in @specs. The
* currently-supported spec types are:
*
* "mac:00:11:22:33:44:55" - matches a device with the given
* hardware address
*
* "interface-name:foo0" - matches a device with the given
* interface name
*
* "s390-subchannels:00.11.22" - matches a device with the given
* z/VM / s390 subchannels.
*
* "*" - matches any device
*
* Returns: #TRUE if @self matches one of the specs in @specs
*/
gboolean
nm_device_spec_match_list(NMDevice *self, const GSList *specs)
{
return nm_device_spec_match_list_full(self, specs, FALSE);
}
int
nm_device_spec_match_list_full(NMDevice *self, const GSList *specs, int no_match_value)
{
NMDeviceClass *klass;
NMMatchSpecMatchType m;
const char *hw_address = NULL;
gboolean is_fake;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
klass = NM_DEVICE_GET_CLASS(self);
hw_address = nm_device_get_permanent_hw_address_full(
self,
!nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT),
&is_fake);
m = nm_match_spec_device(specs,
nm_device_get_iface(self),
nm_device_get_type_description(self),
nm_device_get_driver(self),
nm_device_get_driver_version(self),
is_fake ? NULL : hw_address,
klass->get_s390_subchannels ? klass->get_s390_subchannels(self) : NULL,
nm_dhcp_manager_get_config(nm_dhcp_manager_get()));
switch (m) {
case NM_MATCH_SPEC_MATCH:
return TRUE;
case NM_MATCH_SPEC_NEG_MATCH:
return FALSE;
case NM_MATCH_SPEC_NO_MATCH:
return no_match_value;
}
nm_assert_not_reached();
return no_match_value;
}
guint
nm_device_get_supplicant_timeout(NMDevice *self)
{
NMConnection *connection;
NMSetting8021x *s_8021x;
int timeout;
#define SUPPLICANT_DEFAULT_TIMEOUT 25
g_return_val_if_fail(NM_IS_DEVICE(self), SUPPLICANT_DEFAULT_TIMEOUT);
connection = nm_device_get_applied_connection(self);
g_return_val_if_fail(connection, SUPPLICANT_DEFAULT_TIMEOUT);
s_8021x = nm_connection_get_setting_802_1x(connection);
if (s_8021x) {
timeout = nm_setting_802_1x_get_auth_timeout(s_8021x);
if (timeout > 0)
return timeout;
}
return nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("802-1x.auth-timeout"),
self,
1,
G_MAXINT32,
SUPPLICANT_DEFAULT_TIMEOUT);
}
gboolean
nm_device_auth_retries_try_next(NMDevice *self)
{
NMDevicePrivate *priv;
NMSettingConnection *s_con;
int auth_retries;
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
auth_retries = priv->auth_retries;
if (G_UNLIKELY(auth_retries == NM_DEVICE_AUTH_RETRIES_UNSET)) {
auth_retries = -1;
s_con = nm_device_get_applied_setting(self, NM_TYPE_SETTING_CONNECTION);
if (s_con)
auth_retries = nm_setting_connection_get_auth_retries(s_con);
if (auth_retries == -1) {
auth_retries = nm_config_data_get_connection_default_int64(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.auth-retries"),
self,
-1,
G_MAXINT32,
-1);
}
if (auth_retries == 0)
auth_retries = NM_DEVICE_AUTH_RETRIES_INFINITY;
else if (auth_retries == -1)
auth_retries = NM_DEVICE_AUTH_RETRIES_DEFAULT;
else
nm_assert(auth_retries > 0);
priv->auth_retries = auth_retries;
}
if (auth_retries == NM_DEVICE_AUTH_RETRIES_INFINITY)
return TRUE;
if (auth_retries <= 0) {
nm_assert(auth_retries == 0);
return FALSE;
}
priv->auth_retries--;
return TRUE;
}
static void
hostname_dns_lookup_callback(GObject *source, GAsyncResult *result, gpointer user_data)
{
HostnameResolver *resolver;
NMDevice *self;
gs_free char *addr_str = NULL;
gs_free char *output = NULL;
gs_free_error GError *error = NULL;
output = nm_device_resolve_address_finish(result, &error);
if (g_error_matches(error, G_IO_ERROR, G_IO_ERROR_CANCELLED))
return;
resolver = user_data;
self = resolver->device;
resolver->state = RESOLVER_DONE;
if (error) {
_LOGD(LOGD_DNS,
"hostname-from-dns: lookup error for %s: %s",
(addr_str = g_inet_address_to_string(resolver->address)),
error->message);
} else {
gboolean valid;
resolver->hostname = g_steal_pointer(&output);
valid = nm_utils_validate_hostname(resolver->hostname);
_LOGD(LOGD_DNS,
"hostname-from-dns: lookup done for %s, result %s%s%s%s",
(addr_str = g_inet_address_to_string(resolver->address)),
NM_PRINT_FMT_QUOTE_STRING(resolver->hostname),
valid ? "" : " (invalid)");
if (!valid)
nm_clear_g_free(&resolver->hostname);
}
nm_clear_g_cancellable(&resolver->cancellable);
g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
}
static gboolean
hostname_dns_address_timeout(gpointer user_data)
{
HostnameResolver *resolver = user_data;
NMDevice *self = resolver->device;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
nm_assert(resolver->state == RESOLVER_WAIT_ADDRESS);
nm_assert(!resolver->address);
nm_assert(!resolver->cancellable);
_LOGT(LOGD_DNS,
"hostname-from-dns: timed out while waiting IPv%c address",
nm_utils_addr_family_to_char(resolver->addr_family));
resolver->timeout_id = 0;
resolver->state = RESOLVER_DONE;
g_signal_emit(self, signals[DNS_LOOKUP_DONE], 0);
return G_SOURCE_REMOVE;
}
static const char *
_resolver_state_to_string(ResolverState state)
{
switch (state) {
case RESOLVER_WAIT_ADDRESS:
return "wait-address";
case RESOLVER_IN_PROGRESS:
return "in-progress";
case RESOLVER_DONE:
return "done";
default:
nm_assert_not_reached();
return "unknown";
}
}
void
nm_device_clear_dns_lookup_data(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint i;
for (i = 0; i < 2; i++)
nm_clear_pointer(&priv->hostname_resolver_x[i], _hostname_resolver_free);
}
static GInetAddress *
get_address_for_hostname_dns_lookup(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMPLookup lookup;
const NMDedupMultiHeadEntry *head_entry;
const NMDedupMultiEntry *iter;
const guint8 *addr6_ll = NULL;
const guint8 *addr6_nonll = NULL;
int ifindex;
ifindex = nm_device_get_ip_ifindex(self);
if (ifindex <= 0)
return NULL;
/* FIXME(l3cfg): now we lookup the address from platform. Should we instead look
* it up from NML3Cfg? That is, take an address that we want to configure as
* opposed to an address that is configured? */
head_entry = nm_platform_lookup(
nm_device_get_platform(self),
nmp_lookup_init_object(&lookup, NMP_OBJECT_TYPE_IP_ADDRESS(IS_IPv4), ifindex));
if (head_entry) {
c_list_for_each_entry (iter, &head_entry->lst_entries_head, lst_entries) {
const NMPlatformIPAddress *addr = NMP_OBJECT_CAST_IP_ADDRESS(iter->obj);
if (IS_IPv4) {
return g_inet_address_new_from_bytes(addr->address_ptr, G_SOCKET_FAMILY_IPV4);
}
/* For IPv6 prefer, in order:
* - !link-local, !deprecated
* - !link-local, deprecated
* - link-local
*/
if (!IN6_IS_ADDR_LINKLOCAL(addr->address_ptr)) {
if (!(addr->n_ifa_flags & IFA_F_DEPRECATED)) {
return g_inet_address_new_from_bytes(addr->address_ptr, G_SOCKET_FAMILY_IPV6);
}
addr6_nonll = addr->address_ptr;
continue;
}
addr6_ll = addr->address_ptr;
}
if (addr6_nonll || addr6_ll)
return g_inet_address_new_from_bytes(addr6_nonll ?: addr6_ll, G_SOCKET_FAMILY_IPV6);
}
return NULL;
}
/* return value is valid only immediately */
const char *
nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv;
HostnameResolver *resolver;
const char *method;
gboolean address_changed = FALSE;
gs_unref_object GInetAddress *new_address = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
priv = NM_DEVICE_GET_PRIVATE(self);
/* If the device is not supposed to have addresses,
* return an immediate empty result.*/
if (!nm_device_get_applied_connection(self)) {
NM_SET_OUT(out_wait, FALSE);
return NULL;
}
method = nm_device_get_effective_ip_config_method(self, addr_family);
if (IS_IPv4) {
if (NM_IN_STRSET(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED)) {
nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
NM_SET_OUT(out_wait, FALSE);
return NULL;
}
} else {
if (NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_DISABLED,
NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
nm_clear_pointer(&priv->hostname_resolver_x[IS_IPv4], _hostname_resolver_free);
NM_SET_OUT(out_wait, FALSE);
return NULL;
}
}
resolver = priv->hostname_resolver_x[IS_IPv4];
if (!resolver) {
resolver = g_slice_new(HostnameResolver);
*resolver = (HostnameResolver){
.device = self,
.addr_family = addr_family,
.state = RESOLVER_WAIT_ADDRESS,
};
priv->hostname_resolver_x[IS_IPv4] = resolver;
}
/* Determine the most suitable address of the interface
* and whether it changed from the previous lookup */
new_address = get_address_for_hostname_dns_lookup(self, addr_family);
if (new_address && resolver->address) {
if (!g_inet_address_equal(new_address, resolver->address))
address_changed = TRUE;
} else if (new_address != resolver->address)
address_changed = TRUE;
{
gs_free char *old_str = NULL;
gs_free char *new_str = NULL;
_LOGT(LOGD_DNS,
"hostname-from-dns: ipv%c resolver state %s, old address %s, new address %s",
nm_utils_addr_family_to_char(resolver->addr_family),
_resolver_state_to_string(resolver->state),
resolver->address ? (old_str = g_inet_address_to_string(resolver->address))
: "(null)",
new_address ? (new_str = g_inet_address_to_string(new_address)) : "(null)");
}
/* In every state, if the address changed, we restart
* the resolution with the new address */
if (address_changed) {
nm_clear_g_cancellable(&resolver->cancellable);
g_clear_object(&resolver->address);
resolver->state = RESOLVER_WAIT_ADDRESS;
}
if (address_changed && new_address) {
resolver->state = RESOLVER_IN_PROGRESS;
resolver->cancellable = g_cancellable_new();
resolver->address = g_steal_pointer(&new_address);
nm_device_resolve_address(addr_family,
g_inet_address_to_bytes(resolver->address),
resolver->cancellable,
hostname_dns_lookup_callback,
resolver);
nm_clear_g_source(&resolver->timeout_id);
}
switch (resolver->state) {
case RESOLVER_WAIT_ADDRESS:
if (!resolver->timeout_id)
resolver->timeout_id = g_timeout_add(30000, hostname_dns_address_timeout, resolver);
NM_SET_OUT(out_wait, TRUE);
return NULL;
case RESOLVER_IN_PROGRESS:
NM_SET_OUT(out_wait, TRUE);
return NULL;
case RESOLVER_DONE:
NM_SET_OUT(out_wait, FALSE);
return resolver->hostname;
}
return nm_assert_unreachable_val(NULL);
}
/*****************************************************************************/
static const char *
_activation_func_to_string(ActivationHandleFunc func)
{
#define FUNC_TO_STRING_CHECK_AND_RETURN(func, f) \
G_STMT_START \
{ \
if ((func) == (f)) \
return #f; \
} \
G_STMT_END
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage1_device_prepare);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage2_device_config);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage3_ip_config);
g_return_val_if_reached("unknown");
}
static GVariant *
_device_get_ports_variant(NMDevice *device)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(device);
SlaveInfo *info;
GVariantBuilder builder;
gboolean any = FALSE;
if (priv->ports_variant)
return priv->ports_variant;
c_list_for_each_entry (info, &priv->slaves, lst_slave) {
const char *path;
if (!NM_DEVICE_GET_PRIVATE(info->slave)->is_enslaved)
continue;
path = nm_dbus_object_get_path(NM_DBUS_OBJECT(info->slave));
if (!path)
continue;
if (!any) {
any = TRUE;
g_variant_builder_init(&builder, G_VARIANT_TYPE("ao"));
}
g_variant_builder_add(&builder, "o", path);
}
priv->ports_variant = any ? g_variant_ref_sink(g_variant_builder_end(&builder))
: g_variant_ref(nm_g_variant_singleton_ao());
return priv->ports_variant;
}
/*****************************************************************************/
static void
get_property(GObject *object, guint prop_id, GValue *value, GParamSpec *pspec)
{
NMDevice *self = NM_DEVICE(object);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
switch (prop_id) {
case PROP_UDI:
/* UDI is (depending on the device type) a path to sysfs and can contain
* non-UTF-8.
* ip link add name $'d\xccf\\c' type dummy */
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->udi, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
break;
case PROP_PATH:
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->path, NM_UTILS_STR_UTF8_SAFE_FLAG_NONE));
break;
case PROP_IFACE:
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->iface, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
break;
case PROP_IP_IFACE:
g_value_set_string(value, priv->prop_ip_iface);
break;
case PROP_IFINDEX:
g_value_set_int(value, priv->ifindex);
break;
case PROP_DRIVER:
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->driver, NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
break;
case PROP_DRIVER_VERSION:
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->driver_version,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
break;
case PROP_FIRMWARE_VERSION:
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(priv->firmware_version,
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
break;
case PROP_CAPABILITIES:
g_value_set_uint(value, (priv->capabilities & ~NM_DEVICE_CAP_INTERNAL_MASK));
break;
case PROP_IP4_ADDRESS:
g_value_set_variant(value, nm_g_variant_singleton_u_0());
break;
case PROP_CARRIER:
g_value_set_boolean(value, priv->carrier);
break;
case PROP_MTU:
g_value_set_uint(value, priv->mtu);
break;
case PROP_IP4_CONFIG:
nm_dbus_utils_g_value_set_object_path(value, priv->l3ipdata_4.ip_config);
break;
case PROP_DHCP4_CONFIG:
nm_dbus_utils_g_value_set_object_path(value, priv->ipdhcp_data_4.config);
break;
case PROP_IP6_CONFIG:
nm_dbus_utils_g_value_set_object_path(value, priv->l3ipdata_6.ip_config);
break;
case PROP_DHCP6_CONFIG:
nm_dbus_utils_g_value_set_object_path(value, priv->ipdhcp_data_6.config);
break;
case PROP_STATE:
g_value_set_uint(value, priv->state);
break;
case PROP_STATE_REASON:
g_value_take_variant(value, g_variant_new("(uu)", priv->state, priv->state_reason));
break;
case PROP_ACTIVE_CONNECTION:
g_value_set_string(value, nm_dbus_track_obj_path_get(&priv->act_request));
break;
case PROP_DEVICE_TYPE:
g_value_set_uint(value, priv->type);
break;
case PROP_LINK_TYPE:
g_value_set_uint(value, priv->link_type);
break;
case PROP_MANAGED:
/* The managed state exposed on D-Bus only depends on the current device state alone. */
g_value_set_boolean(value, nm_device_get_state(self) > NM_DEVICE_STATE_UNMANAGED);
break;
case PROP_AUTOCONNECT:
g_value_set_boolean(
value,
nm_device_autoconnect_blocked_get(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL) ? FALSE
: TRUE);
break;
case PROP_FIRMWARE_MISSING:
g_value_set_boolean(value, priv->firmware_missing);
break;
case PROP_NM_PLUGIN_MISSING:
g_value_set_boolean(value, priv->nm_plugin_missing);
break;
case PROP_TYPE_DESC:
g_value_set_string(value, priv->type_desc);
break;
case PROP_AVAILABLE_CONNECTIONS:
nm_dbus_utils_g_value_set_object_path_from_hash(value, priv->available_connections, TRUE);
break;
case PROP_PHYSICAL_PORT_ID:
g_value_set_string(value, priv->physical_port_id);
break;
case PROP_MASTER:
g_value_set_object(value, nm_device_get_master(self));
break;
case PROP_PARENT:
g_value_set_string(value, nm_dbus_track_obj_path_get(&priv->parent_device));
break;
case PROP_HW_ADDRESS:
g_value_set_string(value, priv->hw_addr);
break;
case PROP_PERM_HW_ADDRESS:
{
const char *perm_hw_addr;
gboolean perm_hw_addr_is_fake;
perm_hw_addr = nm_device_get_permanent_hw_address_full(self, FALSE, &perm_hw_addr_is_fake);
/* this property is exposed on D-Bus for NMDeviceEthernet and NMDeviceWifi. */
g_value_set_string(value, perm_hw_addr && !perm_hw_addr_is_fake ? perm_hw_addr : NULL);
break;
}
case PROP_HAS_PENDING_ACTION:
g_value_set_boolean(value, nm_device_has_pending_action(self));
break;
case PROP_METERED:
g_value_set_uint(value, priv->metered);
break;
case PROP_LLDP_NEIGHBORS:
g_value_set_variant(value,
priv->lldp_listener
? nm_lldp_listener_get_neighbors(priv->lldp_listener)
: nm_g_variant_singleton_aaLsvI());
break;
case PROP_REAL:
g_value_set_boolean(value, nm_device_is_real(self));
break;
case PROP_SLAVES:
case PROP_PORTS:
g_value_set_variant(value, _device_get_ports_variant(self));
break;
case PROP_STATISTICS_REFRESH_RATE_MS:
g_value_set_uint(value, priv->stats.refresh_rate_ms);
break;
case PROP_STATISTICS_TX_BYTES:
g_value_set_uint64(value, priv->stats.tx_bytes);
break;
case PROP_STATISTICS_RX_BYTES:
g_value_set_uint64(value, priv->stats.rx_bytes);
break;
case PROP_IP4_CONNECTIVITY:
g_value_set_uint(value, priv->concheck_x[1].state);
break;
case PROP_IP6_CONNECTIVITY:
g_value_set_uint(value, priv->concheck_x[0].state);
break;
case PROP_INTERFACE_FLAGS:
g_value_set_uint(value, priv->interface_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)
{
NMDevice *self = (NMDevice *) object;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
switch (prop_id) {
case PROP_UDI:
/* construct-only */
priv->udi = g_value_dup_string(value);
break;
case PROP_IFACE:
/* construct-only */
priv->iface_ = g_value_dup_string(value);
break;
case PROP_DRIVER:
/* construct-only */
priv->driver = g_value_dup_string(value);
break;
case PROP_MANAGED:
/* via D-Bus */
if (nm_device_is_real(self)) {
gboolean managed;
NMDeviceStateReason reason;
managed = g_value_get_boolean(value);
if (managed) {
reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
if (NM_IN_SET_TYPED(NMDeviceSysIfaceState,
priv->sys_iface_state,
NM_DEVICE_SYS_IFACE_STATE_EXTERNAL,
NM_DEVICE_SYS_IFACE_STATE_REMOVED))
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_ASSUME);
} else {
reason = NM_DEVICE_STATE_REASON_REMOVED;
nm_device_sys_iface_state_set(self, NM_DEVICE_SYS_IFACE_STATE_REMOVED);
}
nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_USER_EXPLICIT, !managed, reason);
}
break;
case PROP_AUTOCONNECT:
/* via D-Bus */
if (g_value_get_boolean(value))
nm_device_autoconnect_blocked_unset(self, NM_DEVICE_AUTOCONNECT_BLOCKED_ALL);
else
nm_device_autoconnect_blocked_set(self, NM_DEVICE_AUTOCONNECT_BLOCKED_USER);
break;
case PROP_NM_PLUGIN_MISSING:
/* construct-only */
priv->nm_plugin_missing = g_value_get_boolean(value);
break;
case PROP_DEVICE_TYPE:
/* construct-only */
nm_assert(priv->type == NM_DEVICE_TYPE_UNKNOWN);
priv->type = g_value_get_uint(value);
nm_assert(priv->type > NM_DEVICE_TYPE_UNKNOWN);
nm_assert(priv->type <= NM_DEVICE_TYPE_VRF);
break;
case PROP_LINK_TYPE:
/* construct-only */
nm_assert(priv->link_type == NM_LINK_TYPE_NONE);
priv->link_type = g_value_get_uint(value);
break;
case PROP_TYPE_DESC:
/* construct-only */
priv->type_desc = g_value_dup_string(value);
break;
case PROP_PERM_HW_ADDRESS:
/* construct-only */
priv->hw_addr_perm = g_value_dup_string(value);
break;
case PROP_STATISTICS_REFRESH_RATE_MS:
/* via D-Bus */
_stats_set_refresh_rate(self, g_value_get_uint(value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID(object, prop_id, pspec);
break;
}
}
/*****************************************************************************/
static void
nm_device_init(NMDevice *self)
{
NMDevicePrivate *priv;
priv = G_TYPE_INSTANCE_GET_PRIVATE(self, NM_TYPE_DEVICE, NMDevicePrivate);
self->_priv = priv;
c_list_init(&priv->concheck_lst_head);
c_list_init(&self->devices_lst);
c_list_init(&priv->slaves);
priv->ipdhcp_data_6.v6.mode = NM_NDISC_DHCP_LEVEL_NONE;
priv->concheck_x[0].state = NM_CONNECTIVITY_UNKNOWN;
priv->concheck_x[1].state = NM_CONNECTIVITY_UNKNOWN;
nm_dbus_track_obj_path_init(&priv->parent_device, G_OBJECT(self), obj_properties[PROP_PARENT]);
nm_dbus_track_obj_path_init(&priv->act_request,
G_OBJECT(self),
obj_properties[PROP_ACTIVE_CONNECTION]);
priv->netns = g_object_ref(NM_NETNS_GET);
priv->autoconnect_blocked_flags = DEFAULT_AUTOCONNECT ? NM_DEVICE_AUTOCONNECT_BLOCKED_NONE
: NM_DEVICE_AUTOCONNECT_BLOCKED_USER;
priv->auth_retries = NM_DEVICE_AUTH_RETRIES_UNSET;
priv->type = NM_DEVICE_TYPE_UNKNOWN;
priv->capabilities = NM_DEVICE_CAP_NM_SUPPORTED;
priv->state = NM_DEVICE_STATE_UNMANAGED;
priv->state_reason = NM_DEVICE_STATE_REASON_NONE;
priv->unmanaged_flags = NM_UNMANAGED_PLATFORM_INIT;
priv->unmanaged_mask = priv->unmanaged_flags;
priv->available_connections = g_hash_table_new_full(nm_direct_hash, NULL, g_object_unref, NULL);
priv->ip6_saved_properties = g_hash_table_new_full(nm_str_hash, g_str_equal, NULL, g_free);
priv->sys_iface_state_ = NM_DEVICE_SYS_IFACE_STATE_EXTERNAL;
priv->promisc_reset = NM_OPTION_BOOL_DEFAULT;
}
static GObject *
constructor(GType type, guint n_construct_params, GObjectConstructParam *construct_params)
{
GObject *object;
GObjectClass *klass;
NMDevice *self;
NMDevicePrivate *priv;
const NMPlatformLink *pllink;
klass = G_OBJECT_CLASS(nm_device_parent_class);
object = klass->constructor(type, n_construct_params, construct_params);
if (!object)
return NULL;
self = NM_DEVICE(object);
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->iface && G_LIKELY(!nm_utils_get_testing())) {
pllink = nm_platform_link_get_by_ifname(nm_device_get_platform(self), priv->iface);
if (pllink && link_type_compatible(self, pllink->type, NULL, NULL)) {
_set_ifindex(self, pllink->ifindex, FALSE);
priv->up = NM_FLAGS_HAS(pllink->n_ifi_flags, IFF_UP);
}
}
if (priv->hw_addr_perm) {
guint8 buf[_NM_UTILS_HWADDR_LEN_MAX];
gsize l;
if (!_nm_utils_hwaddr_aton(priv->hw_addr_perm, buf, sizeof(buf), &l)) {
nm_clear_g_free(&priv->hw_addr_perm);
g_return_val_if_reached(object);
}
priv->hw_addr_len_ = l;
priv->hw_addr = nm_utils_hwaddr_ntoa(buf, l);
_LOGT(LOGD_DEVICE, "hw-addr: has permanent hw-address '%s'", priv->hw_addr_perm);
}
return object;
}
static void
constructed(GObject *object)
{
NMDevice *self = NM_DEVICE(object);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatform *platform;
if (NM_DEVICE_GET_CLASS(self)->get_generic_capabilities)
priv->capabilities |= NM_DEVICE_GET_CLASS(self)->get_generic_capabilities(self);
platform = nm_device_get_platform(self);
g_signal_connect(platform, NM_PLATFORM_SIGNAL_LINK_CHANGED, G_CALLBACK(link_changed_cb), self);
priv->manager = g_object_ref(NM_MANAGER_GET);
priv->settings = g_object_ref(NM_SETTINGS_GET);
g_signal_connect(priv->settings,
NM_SETTINGS_SIGNAL_CONNECTION_ADDED,
G_CALLBACK(cp_connection_added),
self);
g_signal_connect(priv->settings,
NM_SETTINGS_SIGNAL_CONNECTION_UPDATED,
G_CALLBACK(cp_connection_updated),
self);
g_signal_connect(priv->settings,
NM_SETTINGS_SIGNAL_CONNECTION_REMOVED,
G_CALLBACK(cp_connection_removed),
self);
G_OBJECT_CLASS(nm_device_parent_class)->constructed(object);
_LOGD(LOGD_DEVICE, "constructed (%s)", G_OBJECT_TYPE_NAME(self));
}
static void
dispose(GObject *object)
{
NMDevice *self = NM_DEVICE(object);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatform *platform;
NMDeviceConnectivityHandle *con_handle;
gs_free_error GError *cancelled_error = NULL;
_LOGD(LOGD_DEVICE, "disposing");
nm_assert(c_list_is_empty(&self->devices_lst));
while ((con_handle = c_list_first_entry(&priv->concheck_lst_head,
NMDeviceConnectivityHandle,
concheck_lst))) {
if (!cancelled_error)
nm_utils_error_set_cancelled(&cancelled_error, FALSE, "NMDevice");
concheck_handle_complete(con_handle, cancelled_error);
}
nm_clear_g_cancellable(&priv->deactivating_cancellable);
nm_device_assume_state_reset(self);
_parent_set_ifindex(self, 0, FALSE);
platform = nm_device_get_platform(self);
g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(link_changed_cb), self);
nm_clear_g_signal_handler(nm_config_get(), &priv->config_changed_id);
nm_clear_g_signal_handler(priv->manager, &priv->ifindex_changed_id);
_dispatcher_cleanup(self);
nm_pacrunner_manager_remove_clear(&priv->pacrunner_conf_id);
_cleanup_generic_pre(self, CLEANUP_TYPE_KEEP);
nm_assert(c_list_is_empty(&priv->slaves));
/* Let the kernel manage IPv6LL again */
_dev_addrgenmode6_set(self, NM_IN6_ADDR_GEN_MODE_EUI64);
_cleanup_generic_post(self, CLEANUP_TYPE_KEEP);
nm_assert(priv->master_ready_id == 0);
g_hash_table_remove_all(priv->ip6_saved_properties);
nm_clear_g_source(&priv->recheck_assume_id);
nm_clear_g_source(&priv->recheck_available.call_id);
nm_clear_g_source(&priv->check_delete_unrealized_id);
nm_clear_g_source_inst(&priv->stats.timeout_source);
carrier_disconnected_action_cancel(self);
_set_ifindex(self, 0, FALSE);
_set_ifindex(self, 0, TRUE);
if (priv->settings) {
g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_added, self);
g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_updated, self);
g_signal_handlers_disconnect_by_func(priv->settings, cp_connection_removed, self);
}
available_connections_del_all(self);
if (nm_clear_g_source(&priv->carrier_wait_id))
nm_device_remove_pending_action(self, NM_PENDING_ACTION_CARRIER_WAIT, FALSE);
_clear_queued_act_request(priv, NM_ACTIVE_CONNECTION_STATE_REASON_DEVICE_DISCONNECTED);
nm_clear_g_source(&priv->device_link_changed_id);
nm_clear_g_source(&priv->device_ip_link_changed_id);
lldp_setup(self, FALSE);
nm_clear_g_source(&priv->concheck_x[0].p_cur_id);
nm_clear_g_source(&priv->concheck_x[1].p_cur_id);
nm_assert(!priv->sriov.pending);
if (priv->sriov.next) {
nm_g_slice_free(priv->sriov.next);
priv->sriov.next = NULL;
}
g_clear_object(&priv->l3cfg);
g_clear_object(&priv->l3ipdata_4.ip_config);
g_clear_object(&priv->l3ipdata_6.ip_config);
G_OBJECT_CLASS(nm_device_parent_class)->dispose(object);
if (nm_clear_g_source(&priv->queued_state.id)) {
/* FIXME: we'd expect the queud_state to be already cleared and this statement
* not being necessary. Add this check here to hopefully investigate crash
* rh#1270247. */
g_return_if_reached();
}
}
static void
finalize(GObject *object)
{
NMDevice *self = NM_DEVICE(object);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_LOGD(LOGD_DEVICE, "finalize(): %s", G_OBJECT_TYPE_NAME(self));
g_free(priv->hw_addr);
g_free(priv->hw_addr_perm);
g_free(priv->hw_addr_initial);
g_free(priv->pending_actions.arr);
nm_clear_g_free(&priv->physical_port_id);
g_free(priv->udi);
g_free(priv->path);
g_free(priv->iface_);
g_free(priv->ip_iface_);
g_free(priv->driver);
g_free(priv->driver_version);
g_free(priv->firmware_version);
g_free(priv->type_desc);
g_free(priv->current_stable_id);
g_hash_table_unref(priv->ip6_saved_properties);
g_hash_table_unref(priv->available_connections);
nm_dbus_track_obj_path_deinit(&priv->parent_device);
nm_dbus_track_obj_path_deinit(&priv->act_request);
nm_g_variant_unref(priv->ports_variant);
G_OBJECT_CLASS(nm_device_parent_class)->finalize(object);
/* for testing, NMDeviceTest does not invoke NMDevice::constructed,
* and thus @settings might be unset. */
nm_g_object_unref(priv->settings);
nm_g_object_unref(priv->manager);
nm_g_object_unref(priv->concheck_mgr);
g_object_unref(priv->netns);
}
/*****************************************************************************/
static const GDBusSignalInfo signal_info_state_changed = NM_DEFINE_GDBUS_SIGNAL_INFO_INIT(
"StateChanged",
.args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("new_state", "u"),
NM_DEFINE_GDBUS_ARG_INFO("old_state", "u"),
NM_DEFINE_GDBUS_ARG_INFO("reason", "u"), ), );
static const NMDBusInterfaceInfoExtended interface_info_device = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
NM_DBUS_INTERFACE_DEVICE,
.methods = NM_DEFINE_GDBUS_METHOD_INFOS(
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
NM_DEFINE_GDBUS_METHOD_INFO_INIT(
"Reapply",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(
NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"),
NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"),
NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ), ),
.handle = impl_device_reapply, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(
NM_DEFINE_GDBUS_METHOD_INFO_INIT(
"GetAppliedConnection",
.in_args = NM_DEFINE_GDBUS_ARG_INFOS(NM_DEFINE_GDBUS_ARG_INFO("flags", "u"), ),
.out_args = NM_DEFINE_GDBUS_ARG_INFOS(
NM_DEFINE_GDBUS_ARG_INFO("connection", "a{sa{sv}}"),
NM_DEFINE_GDBUS_ARG_INFO("version_id", "t"), ), ),
.handle = impl_device_get_applied_connection, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Disconnect", ),
.handle = impl_device_disconnect, ),
NM_DEFINE_DBUS_METHOD_INFO_EXTENDED(NM_DEFINE_GDBUS_METHOD_INFO_INIT("Delete", ),
.handle = impl_device_delete, ), ),
.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&signal_info_state_changed, ),
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Udi", "s", NM_DEVICE_UDI),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Path", "s", NM_DEVICE_PATH),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Interface", "s", NM_DEVICE_IFACE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("IpInterface", "s", NM_DEVICE_IP_IFACE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Driver", "s", NM_DEVICE_DRIVER),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DriverVersion",
"s",
NM_DEVICE_DRIVER_VERSION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FirmwareVersion",
"s",
NM_DEVICE_FIRMWARE_VERSION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Capabilities",
"u",
NM_DEVICE_CAPABILITIES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Address",
"u",
NM_DEVICE_IP4_ADDRESS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("State", "u", NM_DEVICE_STATE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("StateReason",
"(uu)",
NM_DEVICE_STATE_REASON),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("ActiveConnection",
"o",
NM_DEVICE_ACTIVE_CONNECTION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Config", "o", NM_DEVICE_IP4_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Dhcp4Config",
"o",
NM_DEVICE_DHCP4_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip6Config", "o", NM_DEVICE_IP6_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Dhcp6Config",
"o",
NM_DEVICE_DHCP6_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE("Managed",
"b",
NM_DEVICE_MANAGED,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
NM_AUDIT_OP_DEVICE_MANAGED),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE("Autoconnect",
"b",
NM_DEVICE_AUTOCONNECT,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
NM_AUDIT_OP_DEVICE_AUTOCONNECT),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("FirmwareMissing",
"b",
NM_DEVICE_FIRMWARE_MISSING),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("NmPluginMissing",
"b",
NM_DEVICE_NM_PLUGIN_MISSING),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("DeviceType",
"u",
NM_DEVICE_DEVICE_TYPE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("AvailableConnections",
"ao",
NM_DEVICE_AVAILABLE_CONNECTIONS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("PhysicalPortId",
"s",
NM_DEVICE_PHYSICAL_PORT_ID),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Mtu", "u", NM_DEVICE_MTU),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Metered", "u", NM_DEVICE_METERED),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("LldpNeighbors",
"aa{sv}",
NM_DEVICE_LLDP_NEIGHBORS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Real", "b", NM_DEVICE_REAL),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip4Connectivity",
"u",
NM_DEVICE_IP4_CONNECTIVITY),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ip6Connectivity",
"u",
NM_DEVICE_IP6_CONNECTIVITY),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("InterfaceFlags",
"u",
NM_DEVICE_INTERFACE_FLAGS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("HwAddress", "s", NM_DEVICE_HW_ADDRESS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("Ports", "ao", NM_DEVICE_PORTS), ), ),
};
static const NMDBusInterfaceInfoExtended interface_info_device_statistics = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
NM_DBUS_INTERFACE_DEVICE_STATISTICS,
.properties = NM_DEFINE_GDBUS_PROPERTY_INFOS(
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE(
"RefreshRateMs",
"u",
NM_DEVICE_STATISTICS_REFRESH_RATE_MS,
NM_AUTH_PERMISSION_ENABLE_DISABLE_STATISTICS,
NM_AUDIT_OP_STATISTICS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("TxBytes",
"t",
NM_DEVICE_STATISTICS_TX_BYTES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE("RxBytes",
"t",
NM_DEVICE_STATISTICS_RX_BYTES), ), ),
};
static void
nm_device_class_init(NMDeviceClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS(klass);
NMDBusObjectClass *dbus_object_class = NM_DBUS_OBJECT_CLASS(klass);
g_type_class_add_private(object_class, sizeof(NMDevicePrivate));
dbus_object_class->export_path = NM_DBUS_EXPORT_PATH_NUMBERED(NM_DBUS_PATH "/Devices");
dbus_object_class->interface_infos =
NM_DBUS_INTERFACE_INFOS(&interface_info_device, &interface_info_device_statistics);
object_class->dispose = dispose;
object_class->finalize = finalize;
object_class->set_property = set_property;
object_class->get_property = get_property;
object_class->constructor = constructor;
object_class->constructed = constructed;
klass->link_changed = link_changed;
klass->is_available = is_available;
klass->act_stage2_config = act_stage2_config;
klass->get_ip_method_auto = get_ip_method_auto;
klass->get_type_description = get_type_description;
klass->can_auto_connect = can_auto_connect;
klass->can_update_from_platform_link = can_update_from_platform_link;
klass->check_connection_compatible = check_connection_compatible;
klass->check_connection_available = check_connection_available;
klass->can_unmanaged_external_down = can_unmanaged_external_down;
klass->realize_start_notify = realize_start_notify;
klass->unrealize_notify = unrealize_notify;
klass->carrier_changed_notify = carrier_changed_notify;
klass->get_ip_iface_identifier = get_ip_iface_identifier;
klass->unmanaged_on_quit = unmanaged_on_quit;
klass->deactivate_reset_hw_addr = deactivate_reset_hw_addr;
klass->parent_changed_notify = parent_changed_notify;
klass->can_reapply_change = can_reapply_change;
klass->reapply_connection = reapply_connection;
klass->set_platform_mtu = set_platform_mtu;
klass->rfkill_type = NM_RFKILL_TYPE_UNKNOWN;
obj_properties[PROP_UDI] =
g_param_spec_string(NM_DEVICE_UDI,
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PATH] = g_param_spec_string(NM_DEVICE_PATH,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IFACE] =
g_param_spec_string(NM_DEVICE_IFACE,
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP_IFACE] = g_param_spec_string(NM_DEVICE_IP_IFACE,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DRIVER] =
g_param_spec_string(NM_DEVICE_DRIVER,
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DRIVER_VERSION] =
g_param_spec_string(NM_DEVICE_DRIVER_VERSION,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_FIRMWARE_VERSION] =
g_param_spec_string(NM_DEVICE_FIRMWARE_VERSION,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CAPABILITIES] =
g_param_spec_uint(NM_DEVICE_CAPABILITIES,
"",
"",
0,
G_MAXUINT32,
NM_DEVICE_CAP_NONE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_CARRIER] = g_param_spec_boolean(NM_DEVICE_CARRIER,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_MTU] = g_param_spec_uint(NM_DEVICE_MTU,
"",
"",
0,
G_MAXUINT32,
1500,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP4_ADDRESS] =
g_param_spec_variant(NM_DEVICE_IP4_ADDRESS,
"",
"",
G_VARIANT_TYPE_UINT32,
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP4_CONFIG] =
g_param_spec_string(NM_DEVICE_IP4_CONFIG,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DHCP4_CONFIG] =
g_param_spec_string(NM_DEVICE_DHCP4_CONFIG,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP6_CONFIG] =
g_param_spec_string(NM_DEVICE_IP6_CONFIG,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DHCP6_CONFIG] =
g_param_spec_string(NM_DEVICE_DHCP6_CONFIG,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STATE] = g_param_spec_uint(NM_DEVICE_STATE,
"",
"",
0,
G_MAXUINT32,
NM_DEVICE_STATE_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STATE_REASON] =
g_param_spec_variant(NM_DEVICE_STATE_REASON,
"",
"",
G_VARIANT_TYPE("(uu)"),
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_ACTIVE_CONNECTION] =
g_param_spec_string(NM_DEVICE_ACTIVE_CONNECTION,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_DEVICE_TYPE] =
g_param_spec_uint(NM_DEVICE_DEVICE_TYPE,
"",
"",
0,
G_MAXUINT32,
NM_DEVICE_TYPE_UNKNOWN,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_LINK_TYPE] =
g_param_spec_uint(NM_DEVICE_LINK_TYPE,
"",
"",
0,
G_MAXUINT32,
NM_LINK_TYPE_NONE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_MANAGED] = g_param_spec_boolean(NM_DEVICE_MANAGED,
"",
"",
FALSE,
G_PARAM_READWRITE | /* via D-Bus */
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_AUTOCONNECT] = g_param_spec_boolean(NM_DEVICE_AUTOCONNECT,
"",
"",
DEFAULT_AUTOCONNECT,
G_PARAM_READWRITE | /* via D-Bus */
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_FIRMWARE_MISSING] =
g_param_spec_boolean(NM_DEVICE_FIRMWARE_MISSING,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_NM_PLUGIN_MISSING] =
g_param_spec_boolean(NM_DEVICE_NM_PLUGIN_MISSING,
"",
"",
FALSE,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_TYPE_DESC] =
g_param_spec_string(NM_DEVICE_TYPE_DESC,
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IFINDEX] = g_param_spec_int(NM_DEVICE_IFINDEX,
"",
"",
0,
G_MAXINT,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_AVAILABLE_CONNECTIONS] =
g_param_spec_boxed(NM_DEVICE_AVAILABLE_CONNECTIONS,
"",
"",
G_TYPE_STRV,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PHYSICAL_PORT_ID] =
g_param_spec_string(NM_DEVICE_PHYSICAL_PORT_ID,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_MASTER] = g_param_spec_object(NM_DEVICE_MASTER,
"",
"",
NM_TYPE_DEVICE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PARENT] = g_param_spec_string(NM_DEVICE_PARENT,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_HW_ADDRESS] =
g_param_spec_string(NM_DEVICE_HW_ADDRESS,
"",
"",
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PERM_HW_ADDRESS] =
g_param_spec_string(NM_DEVICE_PERM_HW_ADDRESS,
"",
"",
NULL,
G_PARAM_READWRITE | G_PARAM_CONSTRUCT_ONLY | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_HAS_PENDING_ACTION] =
g_param_spec_boolean(NM_DEVICE_HAS_PENDING_ACTION,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_METERED] = g_param_spec_uint(NM_DEVICE_METERED,
"",
"",
0,
G_MAXUINT32,
NM_METERED_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_LLDP_NEIGHBORS] =
g_param_spec_variant(NM_DEVICE_LLDP_NEIGHBORS,
"",
"",
G_VARIANT_TYPE("aa{sv}"),
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_REAL] = g_param_spec_boolean(NM_DEVICE_REAL,
"",
"",
FALSE,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_SLAVES] = g_param_spec_variant(NM_DEVICE_SLAVES,
"",
"",
G_VARIANT_TYPE("ao"),
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_PORTS] = g_param_spec_variant(NM_DEVICE_PORTS,
"",
"",
G_VARIANT_TYPE("ao"),
NULL,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STATISTICS_REFRESH_RATE_MS] =
g_param_spec_uint(NM_DEVICE_STATISTICS_REFRESH_RATE_MS,
"",
"",
0,
UINT32_MAX,
0,
G_PARAM_READWRITE | /* via D-Bus */
G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STATISTICS_TX_BYTES] =
g_param_spec_uint64(NM_DEVICE_STATISTICS_TX_BYTES,
"",
"",
0,
UINT64_MAX,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_STATISTICS_RX_BYTES] =
g_param_spec_uint64(NM_DEVICE_STATISTICS_RX_BYTES,
"",
"",
0,
UINT64_MAX,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP4_CONNECTIVITY] =
g_param_spec_uint(NM_DEVICE_IP4_CONNECTIVITY,
"",
"",
NM_CONNECTIVITY_UNKNOWN,
NM_CONNECTIVITY_FULL,
NM_CONNECTIVITY_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_IP6_CONNECTIVITY] =
g_param_spec_uint(NM_DEVICE_IP6_CONNECTIVITY,
"",
"",
NM_CONNECTIVITY_UNKNOWN,
NM_CONNECTIVITY_FULL,
NM_CONNECTIVITY_UNKNOWN,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
obj_properties[PROP_INTERFACE_FLAGS] =
g_param_spec_uint(NM_DEVICE_INTERFACE_FLAGS,
"",
"",
0,
G_MAXUINT32,
0,
G_PARAM_READABLE | G_PARAM_STATIC_STRINGS);
g_object_class_install_properties(object_class, _PROPERTY_ENUMS_LAST, obj_properties);
signals[STATE_CHANGED] = g_signal_new(NM_DEVICE_STATE_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
G_STRUCT_OFFSET(NMDeviceClass, state_changed),
NULL,
NULL,
NULL,
G_TYPE_NONE,
3,
G_TYPE_UINT,
G_TYPE_UINT,
G_TYPE_UINT);
signals[AUTOCONNECT_ALLOWED] = g_signal_new(NM_DEVICE_AUTOCONNECT_ALLOWED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_LAST,
0,
autoconnect_allowed_accumulator,
NULL,
NULL,
G_TYPE_BOOLEAN,
0);
signals[L3CD_CHANGED] = g_signal_new(NM_DEVICE_L3CD_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_POINTER, /* (const NML3ConfigData *l3cd_old) */
G_TYPE_POINTER /* (const NML3ConfigData *l3cd_new) */);
signals[IP6_PREFIX_DELEGATED] =
g_signal_new(NM_DEVICE_IP6_PREFIX_DELEGATED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
1,
G_TYPE_POINTER /* const NMPlatformIP6Address *prefix */);
signals[IP6_SUBNET_NEEDED] = g_signal_new(NM_DEVICE_IP6_SUBNET_NEEDED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals[REMOVED] = g_signal_new(NM_DEVICE_REMOVED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals[RECHECK_AUTO_ACTIVATE] = g_signal_new(NM_DEVICE_RECHECK_AUTO_ACTIVATE,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals[RECHECK_ASSUME] = g_signal_new(NM_DEVICE_RECHECK_ASSUME,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals[DNS_LOOKUP_DONE] = g_signal_new(NM_DEVICE_DNS_LOOKUP_DONE,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
signals[PLATFORM_ADDRESS_CHANGED] = g_signal_new(NM_DEVICE_PLATFORM_ADDRESS_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
0);
}
/* Connection defaults from plugins */
NM_CON_DEFAULT_NOP("cdma.mtu");
NM_CON_DEFAULT_NOP("gsm.mtu");
NM_CON_DEFAULT_NOP("wifi.ap-isolation");
NM_CON_DEFAULT_NOP("wifi.powersave");
NM_CON_DEFAULT_NOP("wifi.wake-on-wlan");
NM_CON_DEFAULT_NOP("wifi-sec.pmf");
NM_CON_DEFAULT_NOP("wifi-sec.fils");