NetworkManager/src/core/devices/nm-device.c

19022 lines
694 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-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 "NetworkManagerUtils.h"
#include "nm-manager.h"
#include "platform/nm-platform.h"
#include "libnm-platform/nm-platform-utils.h"
#include "platform/nmp-object.h"
#include "platform/nmp-rules-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-proxy-config.h"
#include "nm-ip4-config.h"
#include "nm-ip6-config.h"
#include "nm-pacrunner-manager.h"
#include "dnsmasq/nm-dnsmasq-manager.h"
#include "nm-dhcp-config.h"
#include "nm-rfkill-manager.h"
#include "nm-firewall-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 "nm-acd-manager.h"
#include "libnm-core-intern/nm-core-internal.h"
#include "systemd/nm-sd.h"
#include "nm-lldp-listener.h"
#include "nm-audit-manager.h"
#include "nm-connectivity.h"
#include "nm-dbus-interface.h"
#include "nm-device-generic.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
static guint32
dhcp_grace_period_from_timeout(guint32 timeout)
{
#define DHCP_GRACE_PERIOD_MULTIPLIER 2U
nm_assert(timeout > 0);
nm_assert(timeout < G_MAXINT32);
if (timeout < G_MAXUINT32 / DHCP_GRACE_PERIOD_MULTIPLIER)
return timeout * DHCP_GRACE_PERIOD_MULTIPLIER;
return G_MAXUINT32;
}
#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 struct {
CList lst_slave;
NMDevice *slave;
gulong watch_id;
bool slave_is_enslaved;
bool configure;
} SlaveInfo;
typedef struct {
NMDevice *device;
guint idle_add_id;
int ifindex;
} DeleteOnDeactivateData;
typedef struct {
NMDevice * device;
GCancellable * cancellable;
NMPlatformAsyncCallback callback;
gpointer callback_data;
guint num_vfs;
NMOptionBool autoprobe;
} SriovOp;
typedef void (*AcdCallback)(NMDevice *, NMIP4Config **, gboolean);
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). */
L3_CONFIG_DATA_TYPE_LL_4,
L3_CONFIG_DATA_TYPE_AC_6,
L3_CONFIG_DATA_TYPE_DHCP_4,
L3_CONFIG_DATA_TYPE_DHCP_6,
L3_CONFIG_DATA_TYPE_DEV_4,
L3_CONFIG_DATA_TYPE_DEV_6,
L3_CONFIG_DATA_TYPE_SETTING,
_L3_CONFIG_DATA_TYPE_NUM,
_L3_CONFIG_DATA_TYPE_NONE,
} L3ConfigDataType;
typedef struct {
AcdCallback callback;
NMDevice * device;
NMIP4Config **configs;
} AcdData;
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 *orig; /* the original configuration applied to the device */
NMIPConfig *current; /* configuration after external changes. NULL means
* that the original configuration didn't change. */
} AppliedConfig;
typedef struct {
NMDhcpClient *client;
NMDhcpConfig *config;
gulong state_sigid;
guint grace_id;
bool grace_pending : 1;
bool was_active : 1;
} DhcpData;
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;
} EthtoolState;
typedef enum {
RESOLVER_WAIT_ADDRESS = 0,
RESOLVER_IN_PROGRESS,
RESOLVER_DONE,
} ResolverState;
typedef struct {
ResolverState state;
GResolver * resolver;
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,
IP4_CONFIG_CHANGED,
IP6_CONFIG_CHANGED,
IP6_PREFIX_DELEGATED,
IP6_SUBNET_NEEDED,
REMOVED,
RECHECK_AUTO_ACTIVATE,
RECHECK_ASSUME,
DNS_LOOKUP_DONE,
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,
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_RFKILL_TYPE,
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, );
typedef struct _NMDevicePrivate {
bool in_state_changed;
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;
union {
struct {
guint queued_ip_config_id_6;
guint queued_ip_config_id_4;
};
guint queued_ip_config_id_x[2];
};
GSList *pending_actions;
GSList *dad6_failed_addrs;
NMDBusTrackObjPath parent_device;
char *udi;
char *path;
union {
const char *const iface;
char * iface_;
};
union {
const char *const ip_iface;
char * ip_iface_;
};
union {
const int ifindex;
int ifindex_;
};
union {
const int ip_ifindex;
int ip_ifindex_;
};
NMNetnsSharedIPHandle *shared_ip_handle;
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;
bool update_ip_config_completed_v4 : 1;
bool update_ip_config_completed_v6 : 1;
NMDeviceType type;
char * type_desc;
NMLinkType link_type;
NMDeviceCapabilities capabilities;
char * driver;
char * driver_version;
char * firmware_version;
RfKillType rfkill_type;
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 */
NMUtilsStableType current_stable_id_type : 3;
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;
union {
struct {
guint activation_source_id_6;
guint activation_source_id_4; /* for layer2 and IPv4. */
};
guint activation_source_id_x[2];
};
union {
struct {
ActivationHandleFunc activation_source_func_6;
ActivationHandleFunc activation_source_func_4; /* for layer2 and IPv4. */
};
ActivationHandleFunc activation_source_func_x[2];
};
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;
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 {
const NMDeviceSysIfaceState sys_iface_state;
NMDeviceSysIfaceState sys_iface_state_;
};
bool carrier : 1;
bool ignore_carrier : 1;
bool up : 1; /* IFF_UP */
bool v4_commit_first_time : 1;
bool v6_commit_first_time : 1;
bool default_route_metric_penalty_ip4_has : 1;
bool default_route_metric_penalty_ip6_has : 1;
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 ipv6ll_handle : 1; /* TRUE if NM handles the device's IPv6LL address */
bool ipv6ll_has : 1;
bool ndisc_started : 1;
bool device_link_changed_down : 1;
bool concheck_rp_filter_checked : 1;
NMDeviceStageState stage1_sriov_state : 3;
/* Generic DHCP stuff */
char *dhcp_anycast_address;
char *current_stable_id;
/* Proxy Configuration */
NMProxyConfig * proxy_config;
NMPacrunnerConfId *pacrunner_conf_id;
/* IP configuration info. Combined config from VPN, settings, and device */
union {
struct {
NMIP6Config *ip_config_6;
NMIP4Config *ip_config_4;
};
NMIPConfig *ip_config_x[2];
};
/* Config from DHCP, PPP, LLv4, etc */
AppliedConfig dev_ip_config_4;
/* config from the setting */
union {
struct {
NMIP6Config *con_ip_config_6;
NMIP4Config *con_ip_config_4;
};
NMIPConfig *con_ip_config_x[2];
};
/* Stuff added outside NM */
union {
struct {
NMIP6Config *ext_ip_config_6;
NMIP4Config *ext_ip_config_4;
};
NMIPConfig *ext_ip_config_x[2];
};
/* VPNs which use this device */
union {
struct {
GSList *vpn_configs_6;
GSList *vpn_configs_4;
};
GSList *vpn_configs_x[2];
};
/* Extra device configuration, injected by the subclass of NMDevice.
* This is used for example by NMDeviceModem for WWAN configuration. */
union {
struct {
AppliedConfig dev2_ip_config_6;
AppliedConfig dev2_ip_config_4;
};
AppliedConfig dev2_ip_config_x[2];
};
/* DHCPv4 tracking */
struct {
char *pac_url;
} dhcp4;
struct {
/* IP6 config from DHCP */
AppliedConfig ip6_config;
/* Event ID of the current IP6 config from DHCP */
char * event_id;
gulong prefix_sigid;
NMNDiscDHCPLevel mode;
guint needed_prefixes;
} dhcp6;
union {
struct {
DhcpData dhcp_data_6;
DhcpData dhcp_data_4;
};
DhcpData dhcp_data_x[2];
};
struct {
NMLogDomain log_domain;
guint timeout;
guint watch;
GPid pid;
char * binary;
char * address;
guint deadline;
} gw_ping;
/* dnsmasq stuff for shared connections */
NMDnsMasqManager *dnsmasq_manager;
gulong dnsmasq_state_id;
/* Firewall */
FirewallState fw_state : 4;
NMFirewallManager * fw_mgr;
NMFirewallManagerCallId *fw_call;
/* IPv4LL stuff */
sd_ipv4ll *ipv4ll;
guint ipv4ll_timeout;
guint rt6_temporary_not_available_id;
/* IPv4 DAD stuff */
struct {
GSList * dad_list;
NMAcdManager *announcing;
} acd;
union {
struct {
const NMDeviceIPState ip_state_6;
const NMDeviceIPState ip_state_4;
};
union {
const NMDeviceIPState ip_state_x[2];
NMDeviceIPState ip_state_x_[2];
};
};
AppliedConfig ac_ip6_config; /* config from IPv6 autoconfiguration */
NMIP6Config * ext_ip6_config_captured; /* Configuration captured from platform. */
NMIP6Config * dad6_ip6_config;
struct in6_addr ipv6ll_addr;
GHashTable *rt6_temporary_not_available;
NMNDisc * ndisc;
gulong ndisc_changed_id;
gulong ndisc_timeout_id;
NMSettingIP6ConfigPrivacy ndisc_use_tempaddr;
guint linklocal6_timeout_id;
guint8 linklocal6_dad_counter;
GHashTable *ip6_saved_properties;
EthtoolState *ethtool_state;
gboolean needs_ip6_subnet;
/* 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 {
guint timeout_id;
guint refresh_rate_ms;
guint64 tx_bytes;
guint64 rx_bytes;
} stats;
bool mtu_force_set_done : 1;
} 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 nm_device_set_proxy_config(NMDevice *self, const char *pac_url);
static gboolean update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs);
static gboolean nm_device_set_ip_config(NMDevice * self,
int addr_family,
NMIPConfig *config,
gboolean commit,
GPtrArray * ip4_dev_route_blacklist);
static gboolean ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit);
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 addrconf6_start_with_link_ready(NMDevice *self);
static gboolean linklocal6_start(NMDevice *self);
static guint32 default_route_metric_penalty_get(NMDevice *self, int addr_family);
static guint _prop_get_ipv4_dad_timeout(NMDevice *self);
static NMIP6Config *dad6_get_pending_addresses(NMDevice *self);
static void _carrier_wait_check_queued_act_request(NMDevice *self);
static gint64 _get_carrier_wait_ms(NMDevice *self);
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 gboolean queued_ip4_config_change(gpointer user_data);
static gboolean queued_ip6_config_change(gpointer user_data);
static void ip_check_ping_watch_cb(GPid pid, int status, gpointer user_data);
static gboolean ip_config_valid(NMDeviceState state);
static NMActStageReturn dhcp4_start(NMDevice *self);
static gboolean dhcp6_start(NMDevice *self, gboolean wait_for_ll);
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, const NMIP4Config *config);
static void _cancel_activation(NMDevice *self);
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(NMDevice *self);
/*****************************************************************************/
static NM_UTILS_LOOKUP_STR_DEFINE(
queued_state_to_string,
NMDeviceState,
NM_UTILS_LOOKUP_DEFAULT(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "???"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNKNOWN,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNMANAGED,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unmanaged"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_UNAVAILABLE,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "unavailable"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DISCONNECTED,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "disconnected"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_PREPARE,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "prepare"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_CONFIG,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "config"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_NEED_AUTH,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "need-auth"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CONFIG,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-config"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_IP_CHECK,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "ip-check"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_SECONDARIES,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "secondaries"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_ACTIVATED,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "activated"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_DEACTIVATING,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "deactivating"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_FAILED,
NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE "failed"), );
const char *
nm_device_state_to_str(NMDeviceState state)
{
return queued_state_to_string(state) + NM_STRLEN(NM_PENDING_ACTIONPREFIX_QUEUED_STATE_CHANGE);
}
NM_UTILS_LOOKUP_STR_DEFINE(
nm_device_state_reason_to_str,
NMDeviceStateReason,
NM_UTILS_LOOKUP_DEFAULT(NULL),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_UNKNOWN, "unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NONE, "none"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_MANAGED, "managed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NOW_UNMANAGED, "unmanaged"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONFIG_FAILED, "config-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE, "ip-config-unavailable"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED, "ip-config-expired"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NO_SECRETS, "no-secrets"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_DISCONNECT, "supplicant-disconnect"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_CONFIG_FAILED,
"supplicant-config-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_FAILED, "supplicant-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_TIMEOUT, "supplicant-timeout"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_START_FAILED, "ppp-start-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_DISCONNECT, "ppp-disconnect"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PPP_FAILED, "ppp-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_START_FAILED, "dhcp-start-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_ERROR, "dhcp-error"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DHCP_FAILED, "dhcp-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_START_FAILED, "sharing-start-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SHARED_FAILED, "sharing-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED, "autoip-start-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_ERROR, "autoip-error"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_AUTOIP_FAILED, "autoip-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_BUSY, "modem-busy"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_DIAL_TONE, "modem-no-dialtone"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NO_CARRIER, "modem-no-carrier"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_TIMEOUT, "modem-dial-timeout"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_DIAL_FAILED, "modem-dial-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_INIT_FAILED, "modem-init-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_APN_FAILED, "gsm-apn-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_NOT_SEARCHING,
"gsm-registration-idle"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_DENIED,
"gsm-registration-denied"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_TIMEOUT,
"gsm-registration-timeout"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_REGISTRATION_FAILED,
"gsm-registration-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_PIN_CHECK_FAILED, "gsm-pin-check-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_FIRMWARE_MISSING, "firmware-missing"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_REMOVED, "removed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SLEEPING, "sleeping"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_REMOVED, "connection-removed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_USER_REQUESTED, "user-requested"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CARRIER, "carrier-changed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED, "connection-assumed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SUPPLICANT_AVAILABLE, "supplicant-available"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_NOT_FOUND, "modem-not-found"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BT_FAILED, "bluetooth-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_NOT_INSERTED, "gsm-sim-not-inserted"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PIN_REQUIRED, "gsm-sim-pin-required"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_PUK_REQUIRED, "gsm-sim-puk-required"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_GSM_SIM_WRONG, "gsm-sim-wrong"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_INFINIBAND_MODE, "infiniband-mode"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DEPENDENCY_FAILED, "dependency-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_BR2684_FAILED, "br2684-bridge-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_MANAGER_UNAVAILABLE,
"modem-manager-unavailable"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SSID_NOT_FOUND, "ssid-not-found"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SECONDARY_CONNECTION_FAILED,
"secondary-connection-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_DCB_FCOE_FAILED, "dcb-fcoe-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_TEAMD_CONTROL_FAILED, "teamd-control-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_FAILED, "modem-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_MODEM_AVAILABLE, "modem-available"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SIM_PIN_INCORRECT, "sim-pin-incorrect"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_NEW_ACTIVATION, "new-activation"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_CHANGED, "parent-changed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PARENT_MANAGED_CHANGED,
"parent-managed-changed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_OVSDB_FAILED, "ovsdb-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE, "ip-address-duplicate"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_IP_METHOD_UNSUPPORTED, "ip-method-unsupported"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_SRIOV_CONFIGURATION_FAILED,
"sriov-configuration-failed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_STATE_REASON_PEER_NOT_FOUND, "peer-not-found"), );
#define reason_to_string_a(reason) NM_UTILS_LOOKUP_STR_A(nm_device_state_reason_to_str, reason)
static NM_UTILS_LOOKUP_STR_DEFINE(mtu_source_to_str,
NMDeviceMtuSource,
NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_NONE, "none"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_PARENT, "parent"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_IP_CONFIG,
"ip-config"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_MTU_SOURCE_CONNECTION,
"connection"), );
/*****************************************************************************/
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->resolver);
nm_g_object_unref(resolver->address);
g_free(resolver->hostname);
nm_g_slice_free(resolver);
}
/*****************************************************************************/
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 * default_id = NULL;
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) {
default_id =
nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("connection.stable-id"),
self);
stable_id = default_id;
}
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;
gs_free char * duid_default = NULL;
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_default = nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv6.dhcp-duid"),
self);
duid = duid_default;
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_ns()
/ 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_ns() / 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 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 guint
_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);
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;
int timeout_i;
guint32 timeout;
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;
}
/**
* _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;
gs_free char * iaid_str_free = NULL;
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_free = 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);
iaid_str = iaid_str_free;
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, char **out_mud_url)
{
const char * mud_url;
gs_free char *s = NULL;
nm_assert(out_mud_url && !*out_mud_url);
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 (*out_mud_url = g_steal_pointer(&s));
}
return NULL;
}
static GBytes *
_prop_get_ipv4_dhcp_client_id(NMDevice *self, NMConnection *connection, GBytes *hwaddr)
{
NMSettingIPConfig *s_ip4;
const char * client_id;
gs_free char * client_id_default = NULL;
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_default =
nm_config_data_get_connection_default(NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-client-id"),
self);
if (client_id_default && client_id_default[0]) {
/* a non-empty client-id is always valid, see nm_dhcp_utils_client_id_string_to_bytes(). */
client_id = client_id_default;
}
}
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 *config_data_prop = NULL;
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 ? */
config_data_prop = nm_config_data_get_connection_default(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("ipv4.dhcp-vendor-class-identifier"),
self);
if (config_data_prop && nm_utils_validate_dhcp4_vendor_class_id(config_data_prop, NULL))
conn_prop = config_data_prop;
}
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,
char ** out_addr)
{
NMSetting * setting;
const char *addr = NULL;
nm_assert(out_addr && !*out_addr);
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) {
gs_free char *a = NULL;
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. */
a = nm_config_data_get_connection_default(
NM_CONFIG_GET_DATA,
NM_CON_DEFAULT("wifi.mac-address-randomization"),
self);
v = _nm_utils_ascii_str_to_int64(a,
10,
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 = *out_addr = g_steal_pointer(&a);
}
return addr;
}
static const char *
_prop_get_x_generate_mac_address_mask(NMDevice * self,
NMConnection *connection,
gboolean is_wifi,
char ** out_value)
{
NMSetting * setting;
const char *value = NULL;
char * a;
nm_assert(out_value && !*out_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;
}
a = 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);
if (!a)
return NULL;
*out_value = a;
return a;
}
/*****************************************************************************/
static void
_ethtool_features_reset(NMDevice *self, NMPlatform *platform, EthtoolState *ethtool_state)
{
gs_free NMEthtoolFeatureStates *features;
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_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);
}
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);
if (ethtool_state->features || ethtool_state->coalesce || ethtool_state->ring)
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;
}
NMIP4Config *
nm_device_ip4_config_new(NMDevice *self)
{
return nm_ip4_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self));
}
NMIP6Config *
nm_device_ip6_config_new(NMDevice *self)
{
return nm_ip6_config_new(nm_device_get_multi_index(self), nm_device_get_ip_ifindex(self));
}
NMIPConfig *
nm_device_ip_config_new(NMDevice *self, int addr_family)
{
nm_assert_addr_family(addr_family);
return NM_IS_IPv4(addr_family) ? (gpointer) nm_device_ip4_config_new(self)
: (gpointer) nm_device_ip6_config_new(self);
}
NML3ConfigData *
nm_device_create_l3_config_data(NMDevice *self)
{
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);
}
static void
applied_config_clear(AppliedConfig *config)
{
g_clear_object(&config->current);
g_clear_object(&config->orig);
}
static void
applied_config_init(AppliedConfig *config, gpointer ip_config)
{
nm_assert(!ip_config || (!config->orig && !config->current)
|| nm_ip_config_get_addr_family(ip_config)
== nm_ip_config_get_addr_family(config->orig ?: config->current));
nm_assert(!ip_config || NM_IS_IP_CONFIG(ip_config));
nm_g_object_ref(ip_config);
applied_config_clear(config);
config->orig = ip_config;
}
static void
applied_config_init_new(AppliedConfig *config, NMDevice *self, int addr_family)
{
gs_unref_object NMIPConfig *c = nm_device_ip_config_new(self, addr_family);
applied_config_init(config, c);
}
static NMIPConfig *
applied_config_get_current(AppliedConfig *config)
{
return config->current ?: config->orig;
}
static void
applied_config_add_address(AppliedConfig *config, const NMPlatformIPAddress *address)
{
if (config->orig)
nm_ip_config_add_address(config->orig, address);
else
nm_assert(!config->current);
if (config->current)
nm_ip_config_add_address(config->current, address);
}
static void
applied_config_add_nameserver(AppliedConfig *config, const NMIPAddr *ns)
{
if (config->orig)
nm_ip_config_add_nameserver(config->orig, ns);
else
nm_assert(!config->current);
if (config->current)
nm_ip_config_add_nameserver(config->current, ns);
}
static void
applied_config_add_search(AppliedConfig *config, const char *new)
{
if (config->orig)
nm_ip_config_add_search(config->orig, new);
else
nm_assert(!config->current);
if (config->current)
nm_ip_config_add_search(config->current, new);
}
static void
applied_config_reset_searches(AppliedConfig *config)
{
if (config->orig)
nm_ip_config_reset_searches(config->orig);
else
nm_assert(!config->current);
if (config->current)
nm_ip_config_reset_searches(config->current);
}
static void
applied_config_reset_nameservers(AppliedConfig *config)
{
if (config->orig)
nm_ip_config_reset_nameservers(config->orig);
else
nm_assert(!config->current);
if (config->current)
nm_ip_config_reset_nameservers(config->current);
}
/*****************************************************************************/
static NM_UTILS_LOOKUP_STR_DEFINE(
_sys_iface_state_to_str,
NMDeviceSysIfaceState,
NM_UTILS_LOOKUP_DEFAULT_NM_ASSERT("unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_EXTERNAL, "external"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_ASSUME, "assume"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_MANAGED, "managed"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_SYS_IFACE_STATE_REMOVED, "removed"), );
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",
_sys_iface_state_to_str(priv->sys_iface_state),
_sys_iface_state_to_str(sys_iface_state));
priv->sys_iface_state_ = sys_iface_state;
}
/* 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);
}
/*****************************************************************************/
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 void
init_ip_config_dns_priority(NMDevice *self, NMIPConfig *config)
{
const char *property;
int priority;
property = (nm_ip_config_get_addr_family(config) == AF_INET)
? NM_CON_DEFAULT("ipv4.dns-priority")
: NM_CON_DEFAULT("ipv6.dns-priority");
priority = nm_config_data_get_connection_default_int64(NM_CONFIG_GET_DATA,
property,
self,
G_MININT,
G_MAXINT,
0);
if (priority == 0) {
priority =
nm_device_is_vpn(self) ? NM_DNS_PRIORITY_DEFAULT_VPN : NM_DNS_PRIORITY_DEFAULT_NORMAL;
}
nm_ip_config_set_dns_priority(config, priority);
}
/*****************************************************************************/
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);
}
static void
set_ipv6_token(NMDevice *self, NMUtilsIPv6IfaceId iid, const char *token_str)
{
NMPlatform * platform;
int ifindex;
const NMPlatformLink *link;
char buf[32];
gint64 val;
/* Setting the kernel token is not strictly necessary as the
* IPv6 address is generated in userspace. However it is
* convenient so that users can see the token with iproute
* ('ip token'). */
platform = nm_device_get_platform(self);
ifindex = nm_device_get_ip_ifindex(self);
link = nm_platform_link_get(platform, ifindex);
if (link && link->inet6_token.id == iid.id) {
_LOGT(LOGD_DEVICE | LOGD_IP6, "token %s already set", token_str);
return;
}
/* The kernel allows setting a token only when 'accept_ra'
* is 1: temporarily flip it if necessary; unfortunately
* this will also generate an additional Router Solicitation
* from kernel. */
val = nm_device_sysctl_ip_conf_get_int_checked(self,
AF_INET6,
"accept_ra",
10,
G_MININT32,
G_MAXINT32,
1);
if (val != 1)
nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "1");
nm_platform_link_set_ipv6_token(platform, ifindex, iid);
if (val != 1) {
nm_sprintf_buf(buf, "%d", (int) val);
nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", buf);
}
}
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 NM_UTILS_LOOKUP_STR_DEFINE(_ip_state_to_string,
NMDeviceIPState,
NM_UTILS_LOOKUP_DEFAULT_WARN("unknown"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_NONE, "none"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_WAIT, "wait"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_CONF, "conf"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_DONE, "done"),
NM_UTILS_LOOKUP_STR_ITEM(NM_DEVICE_IP_STATE_FAIL, "fail"), );
static void
_set_ip_state(NMDevice *self, int addr_family, NMDeviceIPState new_state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
nm_assert_addr_family(addr_family);
if (priv->ip_state_x[IS_IPv4] == new_state)
return;
_LOGT(LOGD_DEVICE,
"ip%c-state: set to %d (%s)",
nm_utils_addr_family_to_char(addr_family),
(int) new_state,
_ip_state_to_string(new_state));
priv->ip_state_x_[IS_IPv4] = new_state;
if (new_state == NM_DEVICE_IP_STATE_DONE) {
/* we only set the IPx_READY flag once we reach NM_DEVICE_IP_STATE_DONE state. We don't
* ever clear it, even if we later enter NM_DEVICE_IP_STATE_FAIL state.
*
* This is not documented/guaranteed behavior, but seems to make sense for now. */
_active_connection_set_state_flags(self,
NM_IS_IPv4(addr_family)
? NM_ACTIVATION_STATE_FLAG_IP4_READY
: NM_ACTIVATION_STATE_FLAG_IP6_READY);
}
}
/*****************************************************************************/
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);
int * p_ifindex;
if (ifindex < 0)
ifindex = 0;
p_ifindex = is_ip_ifindex ? &priv->ip_ifindex_ : &priv->ifindex_;
if (*p_ifindex == ifindex)
return FALSE;
*p_ifindex = ifindex;
_LOGD(LOGD_DEVICE, "ifindex: set %sifindex %d", is_ip_ifindex ? "ip-" : "", ifindex);
if (!is_ip_ifindex)
_notify(self, PROP_IFINDEX);
if (priv->manager)
nm_manager_emit_device_ifindex_changed(priv->manager, 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_set_down(platform, ifindex);
success = nm_platform_link_set_name(platform, ifindex, nm_device_get_iface(self));
if (up)
nm_platform_link_set_up(platform, ifindex, NULL);
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);
_set_ifindex(self, ifindex, TRUE);
if (!eq_name) {
g_free(priv->ip_iface_);
priv->ip_iface_ = g_strdup(ifname);
_notify(self, PROP_IP_IFACE);
}
if (priv->ip_ifindex > 0) {
platform = nm_device_get_platform(self);
nm_platform_process_events_ensure_link(platform, priv->ip_ifindex, priv->ip_iface);
if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
nm_platform_link_set_user_ipv6ll_enabled(platform, priv->ip_ifindex, TRUE);
if (!nm_platform_link_is_up(platform, priv->ip_ifindex))
nm_platform_link_set_up(platform, priv->ip_ifindex, NULL);
}
/* 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(&priv->stats.timeout_id);
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_id = g_timeout_add(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
*
* 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)
{
NMSettingIP6Config *s_ip6;
const char * token = NULL;
g_return_val_if_fail(NM_IS_DEVICE(self), 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)
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 gboolean
default_route_metric_penalty_detect(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
/* currently we don't differentiate between IPv4 and IPv6 when detecting
* connectivity. */
if (priv->concheck_x[IS_IPv4].state != NM_CONNECTIVITY_FULL
&& nm_connectivity_check_enabled(concheck_get_mgr(self)))
return TRUE;
return FALSE;
}
static guint32
default_route_metric_penalty_get(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (NM_IS_IPv4(addr_family) ? priv->default_route_metric_penalty_ip4_has
: priv->default_route_metric_penalty_ip6_has)
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;
}
static NMIPRouteTableSyncMode
_get_route_table_sync_mode_stateful(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMDedupMultiIter ipconf_iter;
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 NMPlatformIPRoute *route;
/* If there's a local route switch to all-sync in order
* to properly manage the local table */
nm_ip_config_iter_ip_route_for_each (&ipconf_iter, priv->con_ip_config_x[IS_IPv4], &route) {
if (nm_platform_route_type_uncoerce(route->type_coerced) == RTN_LOCAL) {
all_sync_now = TRUE;
break;
}
}
}
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);
switch (addr_family) {
case AF_INET:
return priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL;
case AF_INET6:
return priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6) : NULL;
case AF_UNSPEC:
return (priv->ip_config_4 ? nm_ip4_config_best_default_route_get(priv->ip_config_4) : NULL)
?: (priv->ip_config_6 ? nm_ip6_config_best_default_route_get(priv->ip_config_6)
: NULL);
default:
g_return_val_if_reached(NULL);
}
}
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;
}
RfKillType
nm_device_get_rfkill_type(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), FALSE);
return NM_DEVICE_GET_PRIVATE(self)->rfkill_type;
}
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)) {
if (nm_device_get_best_default_route(self, AF_INET)
&& !ip_config_merge_and_apply(self, AF_INET, TRUE))
_LOGW(LOGD_IP4, "Failed to update IPv4 route metric");
if (nm_device_get_best_default_route(self, AF_INET6)
&& !ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "Failed to update IPv6 route metric");
}
}
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)
{
NMDevicePrivate *priv;
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);
priv = NM_DEVICE_GET_PRIVATE(self);
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);
/* Send ARP announcements if did not yet and have addresses. */
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !priv->acd.announcing)
nm_device_arp_announce(self);
/* 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) {
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_WAIT)
nm_device_activate_stage3_ip_start(self, AF_INET);
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_WAIT)
nm_device_activate_stage3_ip_start(self, AF_INET6);
}
/* Since slave devices don't have their own IP configuration,
* set the MTU here.
*/
_commit_mtu(slave, NM_DEVICE_GET_PRIVATE(slave)->ip_config_4);
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
is_unmanaged_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
set_unmanaged_external_down(NMDevice *self, gboolean only_if_unmanaged)
{
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 = is_unmanaged_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);
}
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_EXTERNAL_DOWN,
ext_flags,
NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED);
}
void
nm_device_update_dynamic_ip_setup(NMDevice *self)
{
NMDevicePrivate *priv;
GError * error = NULL;
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->dhcp_data_4.client) {
if (!nm_device_dhcp4_renew(self, FALSE)) {
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_DHCP_FAILED);
return;
}
}
if (priv->dhcp_data_6.client) {
if (!nm_device_dhcp6_renew(self, FALSE)) {
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_DHCP_FAILED);
return;
}
}
if (priv->ndisc) {
/* FIXME: todo */
}
if (priv->dnsmasq_manager) {
/* FIXME: todo */
}
if (priv->lldp_listener && nm_lldp_listener_is_running(priv->lldp_listener)) {
nm_lldp_listener_stop(priv->lldp_listener);
if (!nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error)) {
_LOGD(LOGD_DEVICE,
"LLDP listener %p could not be restarted: %s",
priv->lldp_listener,
error->message);
g_clear_error(&error);
}
}
}
/*****************************************************************************/
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 (nm_device_activate_ip4_state_in_wait(self))
nm_device_activate_stage3_ip_start(self, AF_INET);
if (nm_device_activate_ip6_state_in_wait(self))
nm_device_activate_stage3_ip_start(self, AF_INET6);
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)) {
if (carrier)
priv->interface_flags |= NM_DEVICE_INTERFACE_FLAG_CARRIER;
else
priv->interface_flags &= ~NM_DEVICE_INTERFACE_FLAG_CARRIER;
notify_flags = TRUE;
}
priv->carrier = carrier;
if (notify_flags)
nm_gobject_notify_together(self, PROP_CARRIER, PROP_INTERFACE_FLAGS);
else
_notify(self, PROP_CARRIER);
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
ndisc_set_router_config(NMNDisc *ndisc, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_unref_array GArray *addresses = NULL;
gs_unref_array GArray *dns_servers = NULL;
gs_unref_array GArray * dns_domains = NULL;
guint len;
guint i;
const NMDedupMultiHeadEntry *head_entry;
NMDedupMultiIter ipconf_iter;
if (nm_ndisc_get_node_type(ndisc) != NM_NDISC_NODE_TYPE_ROUTER)
return;
head_entry = nm_ip6_config_lookup_addresses(priv->ip_config_6);
addresses =
g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscAddress), head_entry ? head_entry->len : 0);
nm_dedup_multi_iter_for_each (&ipconf_iter, head_entry) {
const NMPlatformIP6Address *addr = NMP_OBJECT_CAST_IP6_ADDRESS(ipconf_iter.current->obj);
NMNDiscAddress * ndisc_addr;
guint32 lifetime;
guint32 preferred;
if (IN6_IS_ADDR_UNSPECIFIED(&addr->address) || IN6_IS_ADDR_LINKLOCAL(&addr->address))
continue;
if (addr->n_ifa_flags & IFA_F_TENTATIVE || addr->n_ifa_flags & IFA_F_DADFAILED)
continue;
if (addr->plen != 64)
continue;
lifetime = nm_utils_lifetime_get(addr->timestamp,
addr->lifetime,
addr->preferred,
NM_NDISC_EXPIRY_BASE_TIMESTAMP / 1000,
&preferred);
if (!lifetime)
continue;
g_array_set_size(addresses, addresses->len + 1);
ndisc_addr = &g_array_index(addresses, NMNDiscAddress, addresses->len - 1);
ndisc_addr->address = addr->address;
ndisc_addr->expiry_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, lifetime);
ndisc_addr->expiry_preferred_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, preferred);
}
len = nm_ip6_config_get_num_nameservers(priv->ip_config_6);
dns_servers = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSServer), len);
g_array_set_size(dns_servers, len);
for (i = 0; i < len; i++) {
const struct in6_addr *nameserver = nm_ip6_config_get_nameserver(priv->ip_config_6, i);
NMNDiscDNSServer * ndisc_nameserver;
ndisc_nameserver = &g_array_index(dns_servers, NMNDiscDNSServer, i);
ndisc_nameserver->address = *nameserver;
ndisc_nameserver->expiry_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
}
len = nm_ip6_config_get_num_searches(priv->ip_config_6);
dns_domains = g_array_sized_new(FALSE, TRUE, sizeof(NMNDiscDNSDomain), len);
g_array_set_size(dns_domains, len);
for (i = 0; i < len; i++) {
const char * search = nm_ip6_config_get_search(priv->ip_config_6, i);
NMNDiscDNSDomain *ndisc_search;
ndisc_search = &g_array_index(dns_domains, NMNDiscDNSDomain, i);
ndisc_search->domain = (char *) search;
ndisc_search->expiry_msec =
_nm_ndisc_lifetime_to_expiry(NM_NDISC_EXPIRY_BASE_TIMESTAMP, NM_NDISC_ROUTER_LIFETIME);
}
nm_ndisc_set_config(ndisc, addresses, dns_servers, dns_domains);
}
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 (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;
}
if (flags != priv->interface_flags) {
priv->interface_flags = flags;
_notify(self, PROP_INTERFACE_FLAGS);
}
}
static gboolean
device_link_changed(NMDevice *self)
{
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))
nm_device_set_unmanaged_by_user_settings(self);
else
update_unmanaged_specs = TRUE;
_notify(self, PROP_IFACE);
if (ip_ifname_changed)
_notify(self, PROP_IP_IFACE);
/* 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->ndisc && pllink->inet6_token.id) {
if (nm_ndisc_set_iid(priv->ndisc, pllink->inet6_token))
_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.
*/
nm_device_queue_recheck_assume(self);
reason = NM_DEVICE_STATE_REASON_CONNECTION_ASSUMED;
}
}
nm_device_set_unmanaged_by_flags(self, NM_UNMANAGED_PLATFORM_INIT, FALSE, reason);
}
set_unmanaged_external_down(self, 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. */
if (NM_IN_SET(priv->ip_state_4, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) {
if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
_LOGW(LOGD_IP4, "failed applying IP4 config after link comes up again");
}
priv->linklocal6_dad_counter = 0;
if (NM_IN_SET(priv->ip_state_6, NM_DEVICE_IP_STATE_CONF, NM_DEVICE_IP_STATE_DONE)) {
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "failed applying IP6 config after link comes up again");
}
}
if (update_unmanaged_specs)
nm_device_set_unmanaged_by_user_settings(self);
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(NMDevice *self)
{
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);
_notify(self, PROP_IP_IFACE);
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((GSourceFunc) 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((GSourceFunc) 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_utils_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_MS
* 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);
gs_free char * value = NULL;
int num_vfs;
if (priv->ifindex > 0 && nm_device_has_capability(self, NM_DEVICE_CAP_SRIOV)) {
value = nm_config_data_get_device_config(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_SRIOV_NUM_VFS,
self,
NULL);
num_vfs = _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, -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))
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 real_rate;
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);
g_return_if_fail(!priv->queued_ip_config_id_4);
g_return_if_fail(!priv->queued_ip_config_id_6);
_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_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
priv->ipv6ll_handle = nm_platform_link_get_user_ipv6ll_enabled(platform, priv->ifindex);
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_id);
real_rate = _stats_refresh_rate_real(priv->stats.refresh_rate_ms);
if (real_rate)
priv->stats.timeout_id = g_timeout_add(real_rate, _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,
is_unmanaged_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->update_ip_config_completed_v4 = FALSE;
priv->update_ip_config_completed_v6 = FALSE;
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);
}
}
nm_clear_g_source(&priv->queued_ip_config_id_4);
nm_clear_g_source(&priv->queued_ip_config_id_6);
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_))
_notify(self, PROP_IP_IFACE);
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(&priv->stats.timeout_id);
_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_str(slave_old_state),
slave_new_state,
nm_device_state_to_str(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);
nm_device_master_release_one_slave(self, info->slave, TRUE, FALSE, reason);
}
}
/**
* 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;
}
static gboolean
get_ip_config_may_fail(NMDevice *self, int addr_family)
{
NMConnection * connection;
NMSettingIPConfig *s_ip;
connection = nm_device_get_applied_connection(self);
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
return !s_ip || nm_setting_ip_config_get_may_fail(s_ip);
}
/*
* check_ip_state
*
* When @full_state_update is TRUE, transition the device from IP_CONFIG to the
* next state according to the outcome of IPv4 and IPv6 configuration. @may_fail
* indicates that we are called just after the initial configuration and thus
* IPv4/IPv6 are allowed to fail if the ipvx.may-fail properties say so, because
* the IP methods couldn't even be started.
* If @full_state_update is FALSE, just check if the connection should be failed
* due to the state of both ip families and the ipvx.may-fail settings.
*/
static void
check_ip_state(NMDevice *self, gboolean may_fail, gboolean full_state_update)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
gboolean ip4_disabled = FALSE, ip6_disabled = FALSE;
NMSettingIPConfig *s_ip4, *s_ip6;
NMDeviceState state;
if (full_state_update && nm_device_get_state(self) != NM_DEVICE_STATE_IP_CONFIG)
return;
/* Don't progress into IP_CHECK or SECONDARIES if we're waiting for the
* master to enslave us. */
if (nm_active_connection_get_master(NM_ACTIVE_CONNECTION(priv->act_request.obj))
&& !priv->is_enslaved)
return;
s_ip4 = nm_device_get_applied_setting(self, NM_TYPE_SETTING_IP4_CONFIG);
if (s_ip4
&& nm_streq0(nm_setting_ip_config_get_method(s_ip4), NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
ip4_disabled = TRUE;
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))
ip6_disabled = TRUE;
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
&& priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
/* Both method completed (or disabled), proceed with activation */
nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
return;
}
if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL
|| (ip4_disabled && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE))
&& (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL
|| (ip6_disabled && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE))) {
/* Either both methods failed, or only one failed and the other is
* disabled */
if (nm_device_sys_iface_state_is_external_or_assume(self)) {
/* We have assumed configuration, but couldn't redo it. No problem,
* move to check state. */
_set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
state = NM_DEVICE_STATE_IP_CHECK;
} else if (may_fail && get_ip_config_may_fail(self, AF_INET)
&& get_ip_config_may_fail(self, AF_INET6)) {
/* Couldn't start either IPv6 and IPv4 autoconfiguration,
* but both are allowed to fail. */
state = NM_DEVICE_STATE_SECONDARIES;
} else {
/* Autoconfiguration attempted without success. */
state = NM_DEVICE_STATE_FAILED;
}
if (full_state_update || state == NM_DEVICE_STATE_FAILED) {
nm_device_state_changed(self, state, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
}
return;
}
/* If a method is still pending but required, wait */
if (priv->ip_state_4 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET))
return;
if (priv->ip_state_6 != NM_DEVICE_IP_STATE_DONE && !get_ip_config_may_fail(self, AF_INET6))
return;
/* If at least a method has completed, proceed with activation */
if ((priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && !ip4_disabled)
|| (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && !ip6_disabled)) {
if (full_state_update)
nm_device_state_changed(self, NM_DEVICE_STATE_IP_CHECK, NM_DEVICE_STATE_REASON_NONE);
return;
}
}
/**
* 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);
_notify(priv->master, PROP_SLAVES);
} else if (activating) {
_LOGW(LOGD_DEVICE,
"Activation: connection '%s' could not be enslaved",
nm_connection_get_id(connection));
}
}
if (activating) {
if (success)
check_ip_state(self, FALSE, TRUE);
else
nm_device_queue_state(self, NM_DEVICE_STATE_FAILED, NM_DEVICE_STATE_REASON_UNKNOWN);
} else
nm_device_queue_recheck_assume(self);
}
/**
* 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);
_notify(priv->master, 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));
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);
}
if (unconfigure_ip_config) {
nm_device_set_ip_config(self, AF_INET, NULL, FALSE, NULL);
nm_device_set_ip_config(self, AF_INET6, NULL, FALSE, NULL);
} else {
if (priv->dhcp_data_4.client)
nm_dhcp_client_stop(priv->dhcp_data_4.client, FALSE);
if (priv->dhcp_data_6.client)
nm_dhcp_client_stop(priv->dhcp_data_6.client, FALSE);
}
}
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);
/* Check for IP configuration. */
if (priv->ip_config_4 && nm_ip4_config_get_num_addresses(priv->ip_config_4))
return TRUE;
if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6))
return TRUE;
/* The existence of a software device is good enough. */
if (nm_device_is_software(self) && nm_device_is_real(self))
return TRUE;
/* Master-slave relationship is also a configuration */
if (!c_list_is_empty(&priv->slaves)
|| nm_platform_link_get_master(nm_device_get_platform(self), priv->ifindex) > 0)
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);
}
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_utils_uuid_generate_buf(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_ip4_config_create_setting(priv->ip_config_4);
nm_connection_add_setting(connection, s_ip4);
s_ip6 = nm_ip6_config_create_setting(priv->ip_config_6, _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;
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;
const char * device_driver;
guint num_patterns = 0;
patterns = nm_setting_match_get_interface_names(s_match, &num_patterns);
if (!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;
device_driver = nm_device_get_driver(self);
patterns = nm_setting_match_get_drivers(s_match, &num_patterns);
if (!nm_wildcard_match_check(device_driver, 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 (!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;
}
}
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
*/
static 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_str(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);
}
static void
dnsmasq_state_changed_cb(NMDnsMasqManager *manager, guint32 status, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
switch (status) {
case NM_DNSMASQ_STATUS_DEAD:
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
break;
default:
break;
}
}
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, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->activation_source_id_x[IS_IPv4] != 0) {
_LOGD(LOGD_DEVICE,
"activation-stage: clear %s,v%c (id %u)",
_activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
nm_utils_addr_family_to_char(addr_family),
priv->activation_source_id_x[IS_IPv4]);
nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
priv->activation_source_func_x[IS_IPv4] = NULL;
}
}
static gboolean
activation_source_handle_cb(NMDevice *self, int addr_family)
{
NMDevicePrivate * priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
ActivationHandleFunc activation_source_func;
guint activation_source_id;
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
priv = NM_DEVICE_GET_PRIVATE(self);
activation_source_func = priv->activation_source_func_x[IS_IPv4];
activation_source_id = priv->activation_source_id_x[IS_IPv4];
g_return_val_if_fail(activation_source_id != 0, G_SOURCE_REMOVE);
nm_assert(activation_source_func);
priv->activation_source_func_x[IS_IPv4] = NULL;
priv->activation_source_id_x[IS_IPv4] = 0;
_LOGD(LOGD_DEVICE,
"activation-stage: invoke %s,v%c (id %u)",
_activation_func_to_string(activation_source_func),
nm_utils_addr_family_to_char(addr_family),
activation_source_id);
activation_source_func(self);
_LOGT(LOGD_DEVICE,
"activation-stage: complete %s,v%c (id %u)",
_activation_func_to_string(activation_source_func),
nm_utils_addr_family_to_char(addr_family),
activation_source_id);
return G_SOURCE_REMOVE;
}
static gboolean
activation_source_handle_cb_4(gpointer user_data)
{
return activation_source_handle_cb(user_data, AF_INET);
}
static gboolean
activation_source_handle_cb_6(gpointer user_data)
{
return activation_source_handle_cb(user_data, AF_INET6);
}
static void
activation_source_schedule(NMDevice *self, ActivationHandleFunc func, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
guint new_id = 0;
if (priv->activation_source_id_x[IS_IPv4] != 0
&& priv->activation_source_func_x[IS_IPv4] == func) {
/* Scheduling the same stage multiple times is fine. */
_LOGT(LOGD_DEVICE,
"activation-stage: already scheduled %s,v%c (id %u)",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family),
priv->activation_source_id_x[IS_IPv4]);
return;
}
new_id =
g_idle_add(IS_IPv4 ? activation_source_handle_cb_4 : activation_source_handle_cb_6, self);
if (priv->activation_source_id_x[IS_IPv4] != 0) {
_LOGD(LOGD_DEVICE,
"activation-stage: schedule %s,v%c which replaces %s,v%c (id %u -> %u)",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family),
_activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
nm_utils_addr_family_to_char(addr_family),
priv->activation_source_id_x[IS_IPv4],
new_id);
nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
} else {
_LOGD(LOGD_DEVICE,
"activation-stage: schedule %s,v%c (id %u)",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family),
new_id);
}
priv->activation_source_func_x[IS_IPv4] = func;
priv->activation_source_id_x[IS_IPv4] = new_id;
}
static void
activation_source_invoke_sync(NMDevice *self, ActivationHandleFunc func, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (priv->activation_source_id_x[IS_IPv4] == 0) {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s,v%c",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family));
} else if (priv->activation_source_func_x[IS_IPv4] == func) {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s,v%c which was already scheduled (id %u)",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family),
priv->activation_source_id_x[IS_IPv4]);
} else {
_LOGD(LOGD_DEVICE,
"activation-stage: synchronously invoke %s,v%c which replaces %s,v%c (id %u)",
_activation_func_to_string(func),
nm_utils_addr_family_to_char(addr_family),
_activation_func_to_string(priv->activation_source_func_x[IS_IPv4]),
nm_utils_addr_family_to_char(addr_family),
priv->activation_source_id_x[IS_IPv4]);
}
nm_clear_g_source(&priv->activation_source_id_x[IS_IPv4]);
priv->activation_source_func_x[IS_IPv4] = NULL;
func(self);
}
/*****************************************************************************/
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 void
lldp_neighbors_changed(NMLldpListener *lldp_listener, GParamSpec *pspec, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
_notify(self, PROP_LLDP_NEIGHBORS);
}
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;
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = FALSE;
_set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_NONE);
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_NONE);
/* Notify the new ActiveConnection along with the state change */
nm_dbus_track_obj_path_set(&priv->act_request, priv->act_request.obj, TRUE);
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,
(GCallback) 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)
{
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_DEVICE_GET_PRIVATE(self)->act_request.obj);
if (!do_sync) {
activation_source_schedule(self, activate_stage1_device_prepare, AF_INET);
return;
}
activation_source_invoke_sync(self, activate_stage1_device_prepare, AF_INET);
}
static NMActStageReturn
act_stage2_config(NMDevice *self, NMDeviceStateReason *out_failure_reason)
{
return NM_ACT_STAGE_RETURN_SUCCESS;
}
static void
lldp_init(NMDevice *self, gboolean restart)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ifindex > 0 && _prop_get_connection_lldp(self)) {
gs_free_error GError *error = NULL;
if (priv->lldp_listener) {
if (restart && nm_lldp_listener_is_running(priv->lldp_listener))
nm_lldp_listener_stop(priv->lldp_listener);
} else {
priv->lldp_listener = nm_lldp_listener_new();
g_signal_connect(priv->lldp_listener,
"notify::" NM_LLDP_LISTENER_NEIGHBORS,
G_CALLBACK(lldp_neighbors_changed),
self);
}
if (!nm_lldp_listener_is_running(priv->lldp_listener)) {
if (nm_lldp_listener_start(priv->lldp_listener, nm_device_get_ifindex(self), &error))
_LOGD(LOGD_DEVICE, "LLDP listener %p started", priv->lldp_listener);
else {
_LOGD(LOGD_DEVICE,
"LLDP listener %p could not be started: %s",
priv->lldp_listener,
error->message);
}
}
} else {
if (priv->lldp_listener)
nm_lldp_listener_stop(priv->lldp_listener);
}
}
/* 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);
NMPRulesManager *rules_manager = nm_netns_get_rules_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;
nmp_rules_manager_set_dirty(rules_manager, user_tag_1);
if (klass->get_extra_rules)
nmp_rules_manager_set_dirty(rules_manager, user_tag_2);
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_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG at
* the same time). */
nmp_rules_manager_track(rules_manager,
&plrule,
10,
user_tag_1,
NMP_RULES_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_rules_manager_track(rules_manager,
NMP_OBJECT_CAST_ROUTING_RULE(extra_rules->pdata[i]),
10,
user_tag_2,
NMP_RULES_MANAGER_EXTERN_WEAKLY_TRACKED_USER_TAG);
}
}
}
}
nmp_rules_manager_untrack_all(rules_manager, user_tag_1, !untrack_only_dirty);
if (klass->get_extra_rules)
nmp_rules_manager_untrack_all(rules_manager, user_tag_2, !untrack_only_dirty);
keep_deleted_rules = FALSE;
if (set_mode == NM_TERNARY_DEFAULT) {
/* when exiting NM, we leave the device up and the rules configured.
* We just all nmp_rules_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_rules_manager_sync(rules_manager, keep_deleted_rules);
}
static gboolean
tc_commit(NMDevice *self)
{
NMConnection * connection = NULL;
gs_unref_ptrarray GPtrArray *qdiscs = NULL;
gs_unref_ptrarray GPtrArray *tfilters = NULL;
NMSettingTCConfig * s_tc = NULL;
NMPlatform * platform;
int ip_ifindex;
platform = nm_device_get_platform(self);
connection = nm_device_get_applied_connection(self);
if (connection)
s_tc = nm_connection_get_setting_tc_config(connection);
ip_ifindex = nm_device_get_ip_ifindex(self);
if (!ip_ifindex)
return s_tc == NULL;
if (s_tc) {
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_qdisc_sync(platform, ip_ifindex, qdiscs))
return FALSE;
if (!nm_platform_tfilter_sync(platform, ip_ifindex, 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;
gboolean no_firmware = FALSE;
CList * iter;
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 (!tc_commit(self)) {
_LOGW(LOGD_IP6, "failed applying traffic control rules");
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
}
_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);
}
lldp_init(self, TRUE);
nm_device_activate_schedule_stage3_ip_config_start(self);
}
void
nm_device_activate_schedule_stage2_device_config(NMDevice *self, gboolean do_sync)
{
g_return_if_fail(NM_IS_DEVICE(self));
if (!do_sync) {
activation_source_schedule(self, activate_stage2_device_config, AF_INET);
return;
}
activation_source_invoke_sync(self, activate_stage2_device_config, AF_INET);
}
void
nm_device_ip_method_failed(NMDevice *self, int addr_family, NMDeviceStateReason reason)
{
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
if (get_ip_config_may_fail(self, addr_family))
check_ip_state(self, FALSE, (nm_device_get_state(self) == NM_DEVICE_STATE_IP_CONFIG));
else
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, reason);
}
/*****************************************************************************/
static void
acd_data_destroy(gpointer ptr)
{
AcdData *data = ptr;
int i;
for (i = 0; data->configs && data->configs[i]; i++)
g_object_unref(data->configs[i]);
g_free(data->configs);
g_slice_free(AcdData, data);
}
static void
ipv4_manual_method_apply(NMDevice *self, NMIP4Config **configs, gboolean success)
{
NMConnection *connection;
const char * method;
connection = nm_device_get_applied_connection(self);
nm_assert(connection);
method = nm_utils_get_ip_config_method(connection, AF_INET);
nm_assert(NM_IN_STRSET(method,
NM_SETTING_IP4_CONFIG_METHOD_MANUAL,
NM_SETTING_IP4_CONFIG_METHOD_AUTO));
if (!success) {
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
return;
}
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_MANUAL))
nm_device_activate_schedule_ip_config_result(self, AF_INET, NULL);
else {
if (NM_DEVICE_GET_PRIVATE(self)->ip_state_4 != NM_DEVICE_IP_STATE_DONE)
ip_config_merge_and_apply(self, AF_INET, TRUE);
}
}
static void
acd_manager_probe_terminated(NMAcdManager *acd_manager, gpointer user_data)
{
AcdData * data = user_data;
NMDevice * self;
NMDevicePrivate * priv;
NMDedupMultiIter ipconf_iter;
const NMPlatformIP4Address *address;
gboolean result, success = TRUE;
int i;
g_assert(data);
self = data->device;
priv = NM_DEVICE_GET_PRIVATE(self);
for (i = 0; data->configs && data->configs[i]; i++) {
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, data->configs[i], &address) {
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
result = nm_acd_manager_check_address(acd_manager, address->address);
success &= result;
_NMLOG(result ? LOGL_DEBUG : LOGL_WARN,
LOGD_DEVICE,
"IPv4 DAD result: address %s is %s",
_nm_utils_inet4_ntop(address->address, sbuf),
result ? "unique" : "duplicate");
}
}
data->callback(self, data->configs, success);
priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager);
nm_acd_manager_free(acd_manager);
}
/**
* ipv4_dad_start:
* @self: device instance
* @configs: NULL-terminated array of IPv4 configurations
* @cb: callback function
*
* Start IPv4 DAD on device @self, check addresses in @configs and call @cb
* when the procedure ends. @cb will be called in any case, even if DAD can't
* be started. @configs will be unreferenced after @cb has been called.
*/
static void
ipv4_dad_start(NMDevice *self, NMIP4Config **configs, AcdCallback cb)
{
static const NMAcdCallbacks acd_callbacks = {
.probe_terminated_callback = acd_manager_probe_terminated,
.user_data_destroy = acd_data_destroy,
};
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMAcdManager * acd_manager;
const NMPlatformIP4Address *address;
NMDedupMultiIter ipconf_iter;
AcdData * data;
guint timeout;
gboolean addr_found;
int r;
const guint8 * hwaddr_arr;
size_t length;
guint i;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(configs);
g_return_if_fail(cb);
for (i = 0, addr_found = FALSE; configs[i]; i++) {
if (nm_ip4_config_get_num_addresses(configs[i]) > 0) {
addr_found = TRUE;
break;
}
}
timeout = _prop_get_ipv4_dad_timeout(self);
hwaddr_arr = nm_platform_link_get_address(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
&length);
if (!timeout || !hwaddr_arr || !addr_found || length != ETH_ALEN
|| nm_device_sys_iface_state_is_external_or_assume(self)) {
/* DAD not needed, signal success */
cb(self, configs, TRUE);
for (i = 0; configs[i]; i++)
g_object_unref(configs[i]);
g_free(configs);
return;
}
data = g_slice_new0(AcdData);
data->configs = configs;
data->callback = cb;
data->device = self;
acd_manager = nm_acd_manager_new(nm_device_get_ip_ifindex(self),
hwaddr_arr,
length,
&acd_callbacks,
data);
priv->acd.dad_list = g_slist_append(priv->acd.dad_list, acd_manager);
for (i = 0; configs[i]; i++) {
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, configs[i], &address)
nm_acd_manager_add_address(acd_manager, address->address);
}
r = nm_acd_manager_start_probe(acd_manager, timeout);
if (r < 0) {
_LOGW(LOGD_DEVICE, "acd probe failed");
/* DAD could not be started, signal success */
cb(self, configs, TRUE);
priv->acd.dad_list = g_slist_remove(priv->acd.dad_list, acd_manager);
nm_acd_manager_free(acd_manager);
}
}
/*****************************************************************************/
/* IPv4LL stuff */
static void
ipv4ll_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipv4ll) {
sd_ipv4ll_set_callback(priv->ipv4ll, NULL, NULL);
sd_ipv4ll_stop(priv->ipv4ll);
priv->ipv4ll = sd_ipv4ll_unref(priv->ipv4ll);
}
nm_clear_g_source(&priv->ipv4ll_timeout);
}
static NMIP4Config *
ipv4ll_get_ip4_config(NMDevice *self, guint32 lla)
{
NMIP4Config * config = NULL;
NMPlatformIP4Address address;
NMPlatformIP4Route route;
config = nm_device_ip4_config_new(self);
g_assert(config);
memset(&address, 0, sizeof(address));
nm_platform_ip4_address_set_addr(&address, lla, 16);
address.addr_source = NM_IP_CONFIG_SOURCE_IP4LL;
nm_ip4_config_add_address(config, &address);
/* Add a multicast route for link-local connections: destination= 224.0.0.0, netmask=240.0.0.0 */
memset(&route, 0, sizeof(route));
route.network = htonl(0xE0000000L);
route.plen = 4;
route.rt_source = NM_IP_CONFIG_SOURCE_IP4LL;
route.table_coerced = nm_platform_route_table_coerce(nm_device_get_route_table(self, AF_INET));
route.metric = nm_device_get_route_metric(self, AF_INET);
nm_ip4_config_add_route(config, &route, NULL);
return config;
}
static void
nm_device_handle_ipv4ll_event(sd_ipv4ll *ll, int event, void *data)
{
NMDevice * self = data;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
struct in_addr address;
NMIP4Config * config;
int r;
if (priv->act_request.obj == NULL)
return;
nm_assert(nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET),
NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL));
switch (event) {
case SD_IPV4LL_EVENT_BIND:
r = sd_ipv4ll_get_address(ll, &address);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "invalid IPv4 link-local address received, error %d.", r);
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
return;
}
if (!nm_utils_ip4_address_is_link_local(address.s_addr)) {
_LOGE(LOGD_AUTOIP4, "invalid address %08x received (not link-local).", address.s_addr);
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_ERROR);
return;
}
config = ipv4ll_get_ip4_config(self, address.s_addr);
if (config == NULL) {
_LOGE(LOGD_AUTOIP4, "failed to get IPv4LL config");
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
return;
}
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
nm_clear_g_source(&priv->ipv4ll_timeout);
nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(config));
} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
applied_config_init(&priv->dev_ip_config_4, config);
if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) {
_LOGE(LOGD_AUTOIP4, "failed to update IP4 config for autoip change.");
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
}
} else
g_assert_not_reached();
g_object_unref(config);
break;
default:
_LOGW(LOGD_AUTOIP4, "IPv4LL address no longer valid after event %d.", event);
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_AUTOIP_FAILED);
}
}
static gboolean
ipv4ll_timeout_cb(gpointer user_data)
{
NMDevice * self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ipv4ll_timeout) {
_LOGI(LOGD_AUTOIP4, "IPv4LL configuration timed out.");
priv->ipv4ll_timeout = 0;
ipv4ll_cleanup(self);
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
nm_device_activate_schedule_ip_config_timeout(self, AF_INET);
}
return FALSE;
}
static NMActStageReturn
ipv4ll_start(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
const struct ether_addr *addr;
int ifindex, r;
size_t addr_len;
ipv4ll_cleanup(self);
r = sd_ipv4ll_new(&priv->ipv4ll);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: new() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
r = sd_ipv4ll_attach_event(priv->ipv4ll, NULL, 0);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: attach_event() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
ifindex = nm_device_get_ip_ifindex(self);
addr = nm_platform_link_get_address(nm_device_get_platform(self), ifindex, &addr_len);
if (!addr || addr_len != ETH_ALEN) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: can't retrieve hardware address");
return NM_ACT_STAGE_RETURN_FAILURE;
}
r = sd_ipv4ll_set_mac(priv->ipv4ll, addr);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: set_mac() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
r = sd_ipv4ll_set_ifindex(priv->ipv4ll, ifindex);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: set_ifindex() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
r = sd_ipv4ll_set_callback(priv->ipv4ll, nm_device_handle_ipv4ll_event, self);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: set_callback() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
r = sd_ipv4ll_start(priv->ipv4ll);
if (r < 0) {
_LOGE(LOGD_AUTOIP4, "IPv4LL: start() failed with error %d", r);
return NM_ACT_STAGE_RETURN_FAILURE;
}
_LOGI(LOGD_DEVICE | LOGD_AUTOIP4, "IPv4LL: started");
/* Start a timeout to bound the address attempt */
priv->ipv4ll_timeout = g_timeout_add_seconds(20, ipv4ll_timeout_cb, self);
return NM_ACT_STAGE_RETURN_POSTPONE;
}
/*****************************************************************************/
static void
ensure_con_ip_config(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMIPConfig * con_ip_config;
if (priv->con_ip_config_x[IS_IPv4])
return;
connection = nm_device_get_applied_connection(self);
if (!connection)
return;
con_ip_config = nm_device_ip_config_new(self, addr_family);
if (IS_IPv4) {
nm_ip4_config_merge_setting(NM_IP4_CONFIG(con_ip_config),
nm_connection_get_setting_ip4_config(connection),
_prop_get_connection_mdns(self),
_prop_get_connection_llmnr(self),
nm_device_get_route_table(self, addr_family),
nm_device_get_route_metric(self, addr_family));
} else {
nm_ip6_config_merge_setting(NM_IP6_CONFIG(con_ip_config),
nm_connection_get_setting_ip6_config(connection),
nm_device_get_route_table(self, addr_family),
nm_device_get_route_metric(self, addr_family));
}
if (nm_device_sys_iface_state_is_external_or_assume(self)) {
/* For assumed connections ignore all addresses and routes. */
nm_ip_config_reset_addresses(con_ip_config);
nm_ip_config_reset_routes(con_ip_config);
}
priv->con_ip_config_x[IS_IPv4] = con_ip_config;
}
/*****************************************************************************/
static void
dhcp4_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->dhcp_data_4.was_active = FALSE;
nm_clear_g_source(&priv->dhcp_data_4.grace_id);
priv->dhcp_data_4.grace_pending = FALSE;
nm_clear_g_free(&priv->dhcp4.pac_url);
if (priv->dhcp_data_4.client) {
/* Stop any ongoing DHCP transaction on this device */
nm_clear_g_signal_handler(priv->dhcp_data_4.client, &priv->dhcp_data_4.state_sigid);
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED)
nm_dhcp_client_stop(priv->dhcp_data_4.client, release);
g_clear_object(&priv->dhcp_data_4.client);
}
if (priv->dhcp_data_4.config) {
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
_notify(self, PROP_DHCP4_CONFIG);
}
}
static gboolean
ip_config_merge_and_apply(NMDevice *self, int addr_family, gboolean commit)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean success;
gs_unref_object NMIPConfig *composite = NULL;
NMIPConfig * config;
gs_unref_ptrarray GPtrArray *ip4_dev_route_blacklist = NULL;
NMConnection * connection;
gboolean ignore_auto_routes = FALSE;
gboolean ignore_auto_dns = FALSE;
gboolean ignore_default_routes = FALSE;
GSList * iter;
const char * ip6_addr_gen_token = NULL;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
if (nm_device_sys_iface_state_is_external(self))
commit = FALSE;
connection = nm_device_get_applied_connection(self);
/* Apply ignore-auto-routes and ignore-auto-dns settings */
if (connection) {
NMSettingIPConfig *s_ip;
s_ip = nm_connection_get_setting_ip_config(connection, addr_family);
if (s_ip) {
ignore_auto_routes = nm_setting_ip_config_get_ignore_auto_routes(s_ip);
ignore_auto_dns = nm_setting_ip_config_get_ignore_auto_dns(s_ip);
/* if the connection has an explicit gateway, we also ignore
* the default routes from other sources. */
ignore_default_routes = nm_setting_ip_config_get_never_default(s_ip)
|| nm_setting_ip_config_get_gateway(s_ip);
if (!IS_IPv4) {
NMSettingIP6Config *s_ip6 = NM_SETTING_IP6_CONFIG(s_ip);
if (nm_setting_ip6_config_get_addr_gen_mode(s_ip6)
== NM_SETTING_IP6_CONFIG_ADDR_GEN_MODE_EUI64)
ip6_addr_gen_token = nm_setting_ip6_config_get_token(s_ip6);
}
}
}
composite = nm_device_ip_config_new(self, addr_family);
if (!IS_IPv4) {
nm_ip6_config_set_privacy(NM_IP6_CONFIG(composite),
priv->ndisc ? priv->ndisc_use_tempaddr
: NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
}
init_ip_config_dns_priority(self, composite);
if (commit) {
if (priv->queued_ip_config_id_x[IS_IPv4])
update_ext_ip_config(self, addr_family, FALSE);
ensure_con_ip_config(self, addr_family);
}
if (!IS_IPv4) {
if (commit && priv->ipv6ll_has) {
const NMPlatformIP6Address ll_a = {
.address = priv->ipv6ll_addr,
.plen = 64,
.addr_source = NM_IP_CONFIG_SOURCE_IP6LL,
};
const NMPlatformIP6Route ll_r = {
.network.s6_addr16[0] = htons(0xfe80u),
.plen = 64,
.metric = nm_device_get_route_metric(self, addr_family),
.rt_source = NM_IP_CONFIG_SOURCE_IP6LL,
};
nm_assert(IN6_IS_ADDR_LINKLOCAL(&priv->ipv6ll_addr));
nm_ip6_config_add_address(NM_IP6_CONFIG(composite), &ll_a);
nm_ip6_config_add_route(NM_IP6_CONFIG(composite), &ll_r, NULL);
}
}
if (commit) {
gboolean v;
v = default_route_metric_penalty_detect(self, addr_family);
if (IS_IPv4)
priv->default_route_metric_penalty_ip4_has = v;
else
priv->default_route_metric_penalty_ip6_has = v;
}
/* Merge all the IP configs into the composite config */
if (IS_IPv4) {
config = applied_config_get_current(&priv->dev_ip_config_4);
if (config) {
nm_ip4_config_merge(
NM_IP4_CONFIG(composite),
NM_IP4_CONFIG(config),
(ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
| (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
| (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
default_route_metric_penalty_get(self, addr_family));
}
}
if (!IS_IPv4) {
config = applied_config_get_current(&priv->ac_ip6_config);
if (config) {
nm_ip6_config_merge(
NM_IP6_CONFIG(composite),
NM_IP6_CONFIG(config),
(ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
| (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
| (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
default_route_metric_penalty_get(self, addr_family));
}
}
if (!IS_IPv4) {
config = applied_config_get_current(&priv->dhcp6.ip6_config);
if (config) {
nm_ip6_config_merge(
NM_IP6_CONFIG(composite),
NM_IP6_CONFIG(config),
(ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
| (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
| (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
default_route_metric_penalty_get(self, addr_family));
}
}
for (iter = priv->vpn_configs_x[IS_IPv4]; iter; iter = iter->next)
nm_ip_config_merge(composite, iter->data, NM_IP_CONFIG_MERGE_DEFAULT, 0);
if (priv->ext_ip_config_x[IS_IPv4])
nm_ip_config_merge(composite,
priv->ext_ip_config_x[IS_IPv4],
NM_IP_CONFIG_MERGE_EXTERNAL,
0);
/* Merge WWAN config *last* to ensure modem-given settings overwrite
* any external stuff set by pppd or other scripts.
*/
config = applied_config_get_current(&priv->dev2_ip_config_x[IS_IPv4]);
if (config) {
nm_ip_config_merge(composite,
config,
(ignore_auto_routes ? NM_IP_CONFIG_MERGE_NO_ROUTES : 0)
| (ignore_default_routes ? NM_IP_CONFIG_MERGE_NO_DEFAULT_ROUTES : 0)
| (ignore_auto_dns ? NM_IP_CONFIG_MERGE_NO_DNS : 0),
default_route_metric_penalty_get(self, addr_family));
}
if (!IS_IPv4) {
if (priv->rt6_temporary_not_available) {
const NMPObject *o;
GHashTableIter hiter;
g_hash_table_iter_init(&hiter, priv->rt6_temporary_not_available);
while (g_hash_table_iter_next(&hiter, (gpointer *) &o, NULL)) {
nm_ip6_config_add_route(NM_IP6_CONFIG(composite),
NMP_OBJECT_CAST_IP6_ROUTE(o),
NULL);
}
}
}
/* Merge user overrides into the composite config. For assumed connections,
* con_ip_config_x is empty. */
if (priv->con_ip_config_x[IS_IPv4]) {
nm_ip_config_merge(composite,
priv->con_ip_config_x[IS_IPv4],
NM_IP_CONFIG_MERGE_DEFAULT,
default_route_metric_penalty_get(self, addr_family));
}
if (commit) {
gboolean is_vrf;
is_vrf = priv->master && nm_device_get_device_type(priv->master) == NM_DEVICE_TYPE_VRF;
if (IS_IPv4) {
nm_ip4_config_add_dependent_routes(NM_IP4_CONFIG(composite),
nm_device_get_route_table(self, addr_family),
nm_device_get_route_metric(self, addr_family),
is_vrf,
&ip4_dev_route_blacklist);
} else {
nm_ip6_config_add_dependent_routes(NM_IP6_CONFIG(composite),
nm_device_get_route_table(self, addr_family),
nm_device_get_route_metric(self, addr_family),
is_vrf);
}
}
if (IS_IPv4) {
if (commit) {
if (NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit)
NM_DEVICE_GET_CLASS(self)->ip4_config_pre_commit(self, NM_IP4_CONFIG(composite));
}
}
if (!IS_IPv4) {
NMUtilsIPv6IfaceId iid;
if (commit && priv->ndisc_started && ip6_addr_gen_token
&& nm_utils_ipv6_interface_identifier_get_from_token(&iid, ip6_addr_gen_token)) {
set_ipv6_token(self, iid, ip6_addr_gen_token);
}
}
success =
nm_device_set_ip_config(self, addr_family, composite, commit, ip4_dev_route_blacklist);
if (commit) {
if (IS_IPv4)
priv->v4_commit_first_time = FALSE;
else
priv->v6_commit_first_time = FALSE;
}
return success;
}
static gboolean
dhcp4_lease_change(NMDevice *self, NMIP4Config *config, gboolean bound)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free_error GError *error = NULL;
g_return_val_if_fail(config, FALSE);
applied_config_init(&priv->dev_ip_config_4, config);
if (!ip_config_merge_and_apply(self, AF_INET, TRUE)) {
_LOGW(LOGD_DHCP4, "failed to update IPv4 config for DHCP change.");
return FALSE;
}
/* TODO: we should perform DAD again whenever we obtain a
* new lease after an expiry. But what should we do if
* a duplicate address is detected? Fail the connection;
* restart DHCP; continue without an address? */
if (bound && !nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
_LOGW(LOGD_DHCP4, "error accepting lease: %s", error->message);
return FALSE;
}
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE, self, NULL, NULL, NULL, NULL);
return TRUE;
}
static gboolean
dhcp_grace_period_expired(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
priv->dhcp_data_x[IS_IPv4].grace_id = 0;
priv->dhcp_data_x[IS_IPv4].grace_pending = FALSE;
_LOGI(LOGD_DHCPX(IS_IPv4),
"DHCPv%c: grace period expired",
nm_utils_addr_family_to_char(addr_family));
nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_IP_CONFIG_EXPIRED);
/* If the device didn't fail, the DHCP client will continue */
return G_SOURCE_REMOVE;
}
static gboolean
dhcp_grace_period_expired_4(gpointer user_data)
{
return dhcp_grace_period_expired(user_data, AF_INET);
}
static gboolean
dhcp_grace_period_expired_6(gpointer user_data)
{
return dhcp_grace_period_expired(user_data, AF_INET6);
}
static gboolean
dhcp_grace_period_start(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
guint32 timeout;
/* In any other case (expired lease, assumed connection, etc.),
* wait for some time before failing the IP method.
*/
if (priv->dhcp_data_x[IS_IPv4].grace_pending) {
/* already pending. */
return FALSE;
}
/* Start a grace period equal to the DHCP timeout multiplied
* by a constant factor. */
timeout = _prop_get_ipvx_dhcp_timeout(self, addr_family);
if (timeout == NM_DHCP_TIMEOUT_INFINITY)
_LOGI(LOGD_DHCPX(IS_IPv4),
"DHCPv%c: trying to acquire a new lease",
nm_utils_addr_family_to_char(addr_family));
else {
timeout = dhcp_grace_period_from_timeout(timeout);
_LOGI(LOGD_DHCPX(IS_IPv4),
"DHCPv%c: trying to acquire a new lease within %u seconds",
nm_utils_addr_family_to_char(addr_family),
timeout);
nm_assert(!priv->dhcp_data_x[IS_IPv4].grace_id);
priv->dhcp_data_x[IS_IPv4].grace_id = g_timeout_add_seconds(
timeout,
IS_IPv4 ? dhcp_grace_period_expired_4 : dhcp_grace_period_expired_6,
self);
}
priv->dhcp_data_x[IS_IPv4].grace_pending = TRUE;
return TRUE;
}
static void
dhcp4_fail(NMDevice *self, NMDhcpState dhcp_state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
_LOGD(LOGD_DHCP4,
"DHCPv4 failed (ip_state %s, was_active %d)",
_ip_state_to_string(priv->ip_state_4),
priv->dhcp_data_4.was_active);
/* The client is always left running after a failure. */
/* Nothing to do if we failed before... */
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
goto clear_config;
/* ... and also if there are static addresses configured
* on the interface.
*/
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_4
&& nm_ip4_config_get_num_addresses(priv->con_ip_config_4) > 0)
goto clear_config;
/* Fail the method when one of the following is true:
* 1) the DHCP client terminated: it does not make sense to start a grace
* period without a client running;
* 2) we failed to get an initial lease AND the client was
* not active before.
*/
if (dhcp_state == NM_DHCP_STATE_TERMINATED
|| (!priv->dhcp_data_4.was_active && priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)) {
nm_device_activate_schedule_ip_config_timeout(self, AF_INET);
return;
}
if (dhcp_grace_period_start(self, AF_INET))
goto clear_config;
return;
clear_config:
/* The previous configuration is no longer valid */
if (priv->dhcp_data_4.config) {
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET);
_notify(self, PROP_DHCP4_CONFIG);
}
}
static void
dhcp4_dad_cb(NMDevice *self, NMIP4Config **configs, gboolean success)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (success) {
nm_device_activate_schedule_ip_config_result(self, AF_INET, NM_IP_CONFIG_CAST(configs[1]));
} else {
nm_dhcp_client_decline(priv->dhcp_data_4.client, "Address conflict detected", NULL);
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_IP_ADDRESS_DUPLICATE);
}
}
static void
dhcp4_state_changed(NMDhcpClient *client,
NMDhcpState state,
NMIP4Config * ip4_config,
GHashTable * options,
gpointer user_data)
{
NMDevice * self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMIP4Config * manual, **configs;
NMConnection * connection;
g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET);
g_return_if_fail(!ip4_config || NM_IS_IP4_CONFIG(ip4_config));
_LOGD(LOGD_DHCP4, "new DHCPv4 client state %d", state);
switch (state) {
case NM_DHCP_STATE_BOUND:
case NM_DHCP_STATE_EXTENDED:
if (!ip4_config) {
_LOGW(LOGD_DHCP4, "failed to get IPv4 config in response to DHCP event.");
dhcp4_fail(self, state);
break;
}
nm_clear_g_source(&priv->dhcp_data_4.grace_id);
priv->dhcp_data_4.grace_pending = FALSE;
/* After some failures, we have been able to renew the lease:
* update the ip state
*/
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_FAIL)
_set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_CONF);
g_free(priv->dhcp4.pac_url);
priv->dhcp4.pac_url = g_strdup(g_hash_table_lookup(options, "wpad"));
nm_device_set_proxy_config(self, priv->dhcp4.pac_url);
nm_dhcp_config_set_options(priv->dhcp_data_4.config, options);
_notify(self, PROP_DHCP4_CONFIG);
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF) {
connection = nm_device_get_applied_connection(self);
g_assert(connection);
manual = nm_device_ip4_config_new(self);
nm_ip4_config_merge_setting(manual,
nm_connection_get_setting_ip4_config(connection),
NM_SETTING_CONNECTION_MDNS_DEFAULT,
NM_SETTING_CONNECTION_LLMNR_DEFAULT,
nm_device_get_route_table(self, AF_INET),
nm_device_get_route_metric(self, AF_INET));
configs = g_new0(NMIP4Config *, 3);
configs[0] = manual;
configs[1] = g_object_ref(ip4_config);
ipv4_dad_start(self, configs, dhcp4_dad_cb);
} else if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
if (dhcp4_lease_change(self, ip4_config, state == NM_DHCP_STATE_BOUND))
nm_device_update_metered(self);
else
dhcp4_fail(self, state);
}
break;
case NM_DHCP_STATE_TIMEOUT:
dhcp4_fail(self, state);
break;
case NM_DHCP_STATE_EXPIRE:
/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_CONF)
break;
/* fall-through */
case NM_DHCP_STATE_DONE:
case NM_DHCP_STATE_FAIL:
case NM_DHCP_STATE_TERMINATED:
dhcp4_fail(self, state);
break;
default:
break;
}
}
static NMActStageReturn
dhcp4_start(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingIPConfig *s_ip4;
gs_unref_bytes GBytes *vendor_class_identifier = NULL;
gs_unref_bytes GBytes *hwaddr = NULL;
gs_unref_bytes GBytes *bcast_hwaddr = NULL;
gs_unref_bytes GBytes *client_id = NULL;
gs_free char * mud_url_free = NULL;
NMConnection * connection;
NMSettingConnection * s_con;
GError * error = NULL;
const NMPlatformLink * pllink;
const char *const * reject_servers;
connection = nm_device_get_applied_connection(self);
g_return_val_if_fail(connection, FALSE);
s_ip4 = nm_connection_get_setting_ip4_config(connection);
s_con = nm_connection_get_setting_connection(connection);
nm_assert(s_con);
/* Clear old exported DHCP options */
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_4.config);
priv->dhcp_data_4.config = nm_dhcp_config_new(AF_INET);
pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
if (pllink) {
hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
bcast_hwaddr = nmp_link_address_get_as_bytes(&pllink->l_broadcast);
}
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_ip4));
reject_servers = nm_setting_ip_config_get_dhcp_reject_servers(s_ip4, NULL);
g_warn_if_fail(priv->dhcp_data_4.client == NULL);
priv->dhcp_data_4.client =
nm_dhcp_manager_start_ip4(nm_dhcp_manager_get(),
nm_netns_get_multi_idx(nm_device_get_netns(self)),
nm_device_get_ip_iface(self),
nm_device_get_ip_ifindex(self),
hwaddr,
bcast_hwaddr,
nm_connection_get_uuid(connection),
nm_device_get_route_table(self, AF_INET),
nm_device_get_route_metric(self, AF_INET),
nm_setting_ip_config_get_dhcp_send_hostname(s_ip4),
nm_setting_ip_config_get_dhcp_hostname(s_ip4),
nm_setting_ip4_config_get_dhcp_fqdn(NM_SETTING_IP4_CONFIG(s_ip4)),
_prop_get_ipvx_dhcp_hostname_flags(self, AF_INET),
_prop_get_connection_mud_url(self, s_con, &mud_url_free),
client_id,
_prop_get_ipvx_dhcp_timeout(self, AF_INET),
priv->dhcp_anycast_address,
NULL,
vendor_class_identifier,
reject_servers,
&error);
if (!priv->dhcp_data_4.client) {
_LOGW(LOGD_DHCP4, "failure to start DHCP: %s", error->message);
g_clear_error(&error);
return NM_ACT_STAGE_RETURN_FAILURE;
}
priv->dhcp_data_4.state_sigid = g_signal_connect(priv->dhcp_data_4.client,
NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
G_CALLBACK(dhcp4_state_changed),
self);
if (nm_device_sys_iface_state_is_external_or_assume(self))
priv->dhcp_data_4.was_active = TRUE;
/* DHCP devices will be notified by the DHCP manager when stuff happens */
return NM_ACT_STAGE_RETURN_POSTPONE;
}
gboolean
nm_device_dhcp4_renew(NMDevice *self, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
g_return_val_if_fail(priv->dhcp_data_4.client != NULL, FALSE);
_LOGI(LOGD_DHCP4, "DHCPv4 lease renewal requested");
/* Terminate old DHCP instance and release the old lease */
dhcp4_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release);
/* Start DHCP again on the interface */
return dhcp4_start(self) != NM_ACT_STAGE_RETURN_FAILURE;
}
/*****************************************************************************/
static NMIP4Config *
shared4_new_config(NMDevice *self, NMConnection *connection)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMIP4Config * config;
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->shared_ip_handle, nm_netns_shared_ip_release);
} else {
if (!priv->shared_ip_handle)
priv->shared_ip_handle = nm_netns_shared_ip_reserve(nm_device_get_netns(self));
nm_platform_ip4_address_set_addr(&address, priv->shared_ip_handle->addr, 24);
}
config = nm_device_ip4_config_new(self);
nm_ip4_config_add_address(config, &address);
return config;
}
/*****************************************************************************/
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;
}
/*****************************************************************************/
/* DHCPv6 stuff */
static void
dhcp6_cleanup(NMDevice *self, CleanupType cleanup_type, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->dhcp_data_6.was_active = FALSE;
priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
applied_config_clear(&priv->dhcp6.ip6_config);
nm_clear_g_free(&priv->dhcp6.event_id);
nm_clear_g_source(&priv->dhcp_data_6.grace_id);
priv->dhcp_data_6.grace_pending = FALSE;
if (priv->dhcp_data_6.client) {
nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp_data_6.state_sigid);
nm_clear_g_signal_handler(priv->dhcp_data_6.client, &priv->dhcp6.prefix_sigid);
if (cleanup_type == CLEANUP_TYPE_DECONFIGURE || cleanup_type == CLEANUP_TYPE_REMOVED)
nm_dhcp_client_stop(priv->dhcp_data_6.client, release);
g_clear_object(&priv->dhcp_data_6.client);
}
if (priv->dhcp_data_6.config) {
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
_notify(self, PROP_DHCP6_CONFIG);
}
}
static gboolean
dhcp6_lease_change(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingsConnection *settings_connection;
if (!applied_config_get_current(&priv->dhcp6.ip6_config)) {
_LOGW(LOGD_DHCP6, "failed to get DHCPv6 config for rebind");
return FALSE;
}
g_assert(priv->dhcp_data_6.client); /* sanity check */
settings_connection = nm_device_get_settings_connection(self);
g_assert(settings_connection);
/* Apply the updated config */
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE)) {
_LOGW(LOGD_DHCP6, "failed to update IPv6 config in response to DHCP event");
return FALSE;
}
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE, self, NULL, NULL, NULL, NULL);
return TRUE;
}
static void
dhcp6_fail(NMDevice *self, NMDhcpState dhcp_state)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gboolean is_dhcp_managed;
_LOGD(LOGD_DHCP6,
"DHCPv6 failed (ip_state %s, was_active %d)",
_ip_state_to_string(priv->ip_state_6),
priv->dhcp_data_6.was_active);
/* The client is always left running after a failure. */
/* Nothing to do if we failed before... */
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
goto clear_config;
is_dhcp_managed = (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED);
if (is_dhcp_managed) {
/* ... and also if there are static addresses configured
* on the interface.
*/
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE && priv->con_ip_config_6
&& nm_ip6_config_get_num_addresses(priv->con_ip_config_6))
goto clear_config;
/* Fail the method when one of the following is true:
* 1) the DHCP client terminated: it does not make sense to start a grace
* period without a client running;
* 2) we failed to get an initial lease AND the client was
* not active before.
*/
if (dhcp_state == NM_DHCP_STATE_TERMINATED
|| (!priv->dhcp_data_6.was_active && priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)) {
nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
return;
}
if (dhcp_grace_period_start(self, AF_INET6))
goto clear_config;
} else {
/* not a hard failure; just live with the RA info */
dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE);
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
}
return;
clear_config:
/* The previous configuration is no longer valid */
if (priv->dhcp_data_6.config) {
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6);
_notify(self, PROP_DHCP6_CONFIG);
}
}
static void
dhcp6_state_changed(NMDhcpClient *client,
NMDhcpState state,
NMIP6Config * ip6_config,
GHashTable * options,
gpointer user_data)
{
NMDevice * self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
gs_free char * event_id = NULL;
g_return_if_fail(nm_dhcp_client_get_addr_family(client) == AF_INET6);
g_return_if_fail(!ip6_config || NM_IS_IP6_CONFIG(ip6_config));
_LOGD(LOGD_DHCP6, "new DHCPv6 client state %d", state);
switch (state) {
case NM_DHCP_STATE_BOUND:
case NM_DHCP_STATE_EXTENDED:
nm_clear_g_source(&priv->dhcp_data_6.grace_id);
priv->dhcp_data_6.grace_pending = FALSE;
/* If the server sends multiple IPv6 addresses, we receive a state
* changed event for each of them. Use the event ID to merge IPv6
* addresses from the same transaction into a single configuration.
*/
event_id = nm_dhcp_utils_get_dhcp6_event_id(options);
if (ip6_config && event_id && priv->dhcp6.event_id
&& nm_streq(event_id, priv->dhcp6.event_id)) {
NMDedupMultiIter ipconf_iter;
const NMPlatformIP6Address *a;
nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, ip6_config, &a)
applied_config_add_address(&priv->dhcp6.ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(a));
} else {
nm_clear_g_free(&priv->dhcp6.event_id);
if (ip6_config) {
applied_config_init(&priv->dhcp6.ip6_config, ip6_config);
priv->dhcp6.event_id = g_strdup(event_id);
nm_dhcp_config_set_options(priv->dhcp_data_6.config, options);
_notify(self, PROP_DHCP6_CONFIG);
} else
applied_config_clear(&priv->dhcp6.ip6_config);
}
/* After long time we have been able to renew the lease:
* update the ip state
*/
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
if (!applied_config_get_current(&priv->dhcp6.ip6_config)) {
nm_device_ip_method_failed(self, AF_INET6, NM_DEVICE_STATE_REASON_DHCP_FAILED);
break;
}
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
} else if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
if (!dhcp6_lease_change(self))
dhcp6_fail(self, state);
break;
case NM_DHCP_STATE_TIMEOUT:
if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED)
dhcp6_fail(self, state);
else {
/* not a hard failure; just live with the RA info */
dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, FALSE);
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF)
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
}
break;
case NM_DHCP_STATE_EXPIRE:
/* Ignore expiry before we even have a lease (NAK, old lease, etc) */
if (priv->ip_state_6 != NM_DEVICE_IP_STATE_CONF)
dhcp6_fail(self, state);
break;
case NM_DHCP_STATE_TERMINATED:
/* In IPv6 info-only mode, the client doesn't handle leases so it
* may exit right after getting a response from the server. That's
* normal. In that case we just ignore the exit.
*/
if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF)
break;
/* fall-through */
case NM_DHCP_STATE_DONE:
case NM_DHCP_STATE_FAIL:
dhcp6_fail(self, state);
break;
default:
break;
}
}
static void
dhcp6_prefix_delegated(NMDhcpClient *client, NMPlatformIP6Address *prefix, gpointer user_data)
{
NMDevice *self = NM_DEVICE(user_data);
/* 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, prefix);
}
/*****************************************************************************/
static gboolean
dhcp6_start_with_link_ready(NMDevice *self, NMConnection *connection)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMSettingIPConfig *s_ip6;
gs_unref_bytes GBytes *hwaddr = NULL;
gs_unref_bytes GBytes * duid = NULL;
gboolean enforce_duid = FALSE;
const NMPlatformLink * pllink;
gs_free char * mud_url_free = NULL;
GError * error = NULL;
guint32 iaid;
gboolean iaid_explicit;
NMSettingConnection * s_con;
const NMPlatformIP6Address *ll_addr = NULL;
g_return_val_if_fail(connection, FALSE);
s_ip6 = nm_connection_get_setting_ip6_config(connection);
nm_assert(s_ip6);
s_con = nm_connection_get_setting_connection(connection);
nm_assert(s_con);
if (priv->ext_ip6_config_captured) {
ll_addr = nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL);
}
if (!ll_addr) {
_LOGW(LOGD_DHCP6, "can't start DHCPv6: no link-local address");
return FALSE;
}
pllink = nm_platform_link_get(nm_device_get_platform(self), nm_device_get_ip_ifindex(self));
if (pllink)
hwaddr = nmp_link_address_get_as_bytes(&pllink->l_address);
iaid = _prop_get_ipvx_dhcp_iaid(self, AF_INET6, connection, TRUE, &iaid_explicit);
duid = _prop_get_ipv6_dhcp_duid(self, connection, hwaddr, &enforce_duid);
priv->dhcp_data_6.client = nm_dhcp_manager_start_ip6(
nm_dhcp_manager_get(),
nm_device_get_multi_index(self),
nm_device_get_ip_iface(self),
nm_device_get_ip_ifindex(self),
&ll_addr->address,
nm_connection_get_uuid(connection),
nm_device_get_route_table(self, AF_INET6),
nm_device_get_route_metric(self, AF_INET6),
nm_setting_ip_config_get_dhcp_send_hostname(s_ip6),
nm_setting_ip_config_get_dhcp_hostname(s_ip6),
_prop_get_ipvx_dhcp_hostname_flags(self, AF_INET6),
_prop_get_connection_mud_url(self, s_con, &mud_url_free),
duid,
enforce_duid,
iaid,
iaid_explicit,
_prop_get_ipvx_dhcp_timeout(self, AF_INET6),
priv->dhcp_anycast_address,
(priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_OTHERCONF) ? TRUE : FALSE,
nm_setting_ip6_config_get_ip6_privacy(NM_SETTING_IP6_CONFIG(s_ip6)),
priv->dhcp6.needed_prefixes,
&error);
if (!priv->dhcp_data_6.client) {
_LOGW(LOGD_DHCP6, "failure to start DHCPv6: %s", error->message);
g_clear_error(&error);
if (nm_device_sys_iface_state_is_external_or_assume(self))
priv->dhcp_data_6.was_active = TRUE;
return FALSE;
}
priv->dhcp_data_6.state_sigid = g_signal_connect(priv->dhcp_data_6.client,
NM_DHCP_CLIENT_SIGNAL_STATE_CHANGED,
G_CALLBACK(dhcp6_state_changed),
self);
priv->dhcp6.prefix_sigid = g_signal_connect(priv->dhcp_data_6.client,
NM_DHCP_CLIENT_SIGNAL_PREFIX_DELEGATED,
G_CALLBACK(dhcp6_prefix_delegated),
self);
if (nm_device_sys_iface_state_is_external_or_assume(self))
priv->dhcp_data_6.was_active = TRUE;
return TRUE;
}
static gboolean
dhcp6_start(NMDevice *self, gboolean wait_for_ll)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
nm_dbus_object_clear_and_unexport(&priv->dhcp_data_6.config);
priv->dhcp_data_6.config = nm_dhcp_config_new(AF_INET6);
nm_assert(!applied_config_get_current(&priv->dhcp6.ip6_config));
applied_config_clear(&priv->dhcp6.ip6_config);
nm_clear_g_free(&priv->dhcp6.event_id);
connection = nm_device_get_applied_connection(self);
g_return_val_if_fail(connection, FALSE);
if (wait_for_ll) {
/* ensure link local is ready... */
if (!linklocal6_start(self)) {
/* wait for the LL address to show up */
return TRUE;
}
/* already have the LL address; kick off DHCP */
}
if (!dhcp6_start_with_link_ready(self, connection))
return FALSE;
return TRUE;
}
gboolean
nm_device_dhcp6_renew(NMDevice *self, gboolean release)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMNDiscDHCPLevel mode;
g_return_val_if_fail(priv->dhcp_data_6.client != NULL, FALSE);
_LOGI(LOGD_DHCP6, "DHCPv6 lease renewal requested");
/* Terminate old DHCP instance and release the old lease */
mode = priv->dhcp6.mode;
dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, release);
priv->dhcp6.mode = mode;
/* Start DHCP again on the interface */
return dhcp6_start(self, FALSE);
}
/*****************************************************************************/
/*
* 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, int needed_prefixes)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->dhcp6.needed_prefixes = needed_prefixes;
if (priv->dhcp_data_6.client) {
_LOGD(LOGD_IP6, "ipv6-pd: asking DHCPv6 for %d prefixes", needed_prefixes);
nm_device_dhcp6_renew(self, FALSE);
} else {
_LOGI(LOGD_IP6, "ipv6-pd: device doesn't use DHCPv6, can't request prefixes");
}
}
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)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMPlatformIP6Address address = *subnet;
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
if (!applied_config_get_current(&priv->ac_ip6_config))
applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
/* Assign a ::1 address in the subnet for us. */
address.address.s6_addr32[3] |= htonl(1);
applied_config_add_address(&priv->ac_ip6_config, NM_PLATFORM_IP_ADDRESS_CAST(&address));
_LOGD(LOGD_IP6,
"ipv6-pd: using %s address (preferred for %u seconds)",
_nm_utils_inet6_ntop(&address.address, sbuf),
subnet->preferred);
/* This also updates the ndisc if there are actual changes. */
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "ipv6-pd: failed applying IP6 config for connection sharing");
}
/*
* 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);
NMIP6Config * from_config = NULL;
guint i, len;
if (applied_config_get_current(&priv->ac_ip6_config)) {
applied_config_reset_nameservers(&priv->ac_ip6_config);
applied_config_reset_searches(&priv->ac_ip6_config);
} else
applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
if (from_device)
from_config = nm_device_get_ip6_config(from_device);
if (!from_config)
return;
len = nm_ip6_config_get_num_nameservers(from_config);
for (i = 0; i < len; i++) {
applied_config_add_nameserver(
&priv->ac_ip6_config,
(const NMIPAddr *) nm_ip6_config_get_nameserver(from_config, i));
}
len = nm_ip6_config_get_num_searches(from_config);
for (i = 0; i < len; i++) {
applied_config_add_search(&priv->ac_ip6_config, nm_ip6_config_get_search(from_config, i));
}
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "ipv6-pd: failed applying DNS config for connection sharing");
}
/*****************************************************************************/
static void
linklocal6_failed(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_g_source(&priv->linklocal6_timeout_id);
nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
}
static gboolean
linklocal6_timeout_cb(gpointer user_data)
{
NMDevice *self = user_data;
_LOGD(LOGD_DEVICE, "linklocal6: waiting for link-local addresses failed due to timeout");
linklocal6_failed(self);
return G_SOURCE_REMOVE;
}
static void
linklocal6_check_complete(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
const char * method;
if (!priv->linklocal6_timeout_id) {
/* we are not waiting for linklocal to complete. Nothing to do. */
return;
}
if (!priv->ext_ip6_config_captured
|| !nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL)) {
/* we don't have a non-tentative link local address yet. Wait longer. */
return;
}
nm_clear_g_source(&priv->linklocal6_timeout_id);
connection = nm_device_get_applied_connection(self);
g_assert(connection);
method = nm_device_get_effective_ip_config_method(self, AF_INET6);
_LOGD(LOGD_DEVICE,
"linklocal6: waiting for link-local addresses successful, continue with method %s",
method);
if (NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NM_SETTING_IP6_CONFIG_METHOD_SHARED))
addrconf6_start_with_link_ready(self);
else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
if (!dhcp6_start_with_link_ready(self, connection)) {
/* Time out IPv6 instead of failing the entire activation */
nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
}
} else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL))
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
else
g_return_if_fail(FALSE);
}
static void
check_and_add_ipv6ll_addr(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
struct in6_addr lladdr;
NMConnection * connection;
NMSettingIP6Config *s_ip6 = NULL;
GError * error = NULL;
const char * addr_type;
char sbuf[NM_UTILS_INET_ADDRSTRLEN];
if (!priv->ipv6ll_handle)
return;
if (priv->ext_ip6_config_captured
&& nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE_TENTATIVE)) {
/* Already have an LL address, nothing to do */
return;
}
priv->ipv6ll_has = FALSE;
memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr));
memset(&lladdr, 0, sizeof(lladdr));
lladdr.s6_addr16[0] = htons(0xfe80);
connection = nm_device_get_applied_connection(self);
if (connection)
s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
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);
if (!nm_utils_ipv6_addr_set_stable_privacy(stable_type,
&lladdr,
nm_device_get_iface(self),
stable_id,
priv->linklocal6_dad_counter++,
&error)) {
_LOGW(LOGD_IP6, "linklocal6: failed to generate an address: %s", error->message);
g_clear_error(&error);
linklocal6_failed(self);
return;
}
addr_type = "stable-privacy";
} else {
NMUtilsIPv6IfaceId iid;
if (priv->linklocal6_timeout_id) {
/* We already started and attempt to add a LL address. For the EUI-64
* mode we can't pick a new one, we'll just fail. */
_LOGW(LOGD_IP6, "linklocal6: DAD failed for an EUI-64 address");
linklocal6_failed(self);
return;
}
if (!nm_device_get_ip_iface_identifier(self, &iid, TRUE)) {
_LOGW(LOGD_IP6, "linklocal6: failed to get interface identifier; IPv6 cannot continue");
return;
}
nm_utils_ipv6_addr_set_interface_identifier(&lladdr, iid);
addr_type = "EUI-64";
}
_LOGD(LOGD_IP6,
"linklocal6: generated %s IPv6LL address %s",
addr_type,
_nm_utils_inet6_ntop(&lladdr, sbuf));
priv->ipv6ll_has = TRUE;
priv->ipv6ll_addr = lladdr;
ip_config_merge_and_apply(self, AF_INET6, TRUE);
}
static gboolean
linklocal6_start(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_g_source(&priv->linklocal6_timeout_id);
if (priv->ext_ip6_config_captured
&& nm_ip6_config_find_first_address(priv->ext_ip6_config_captured,
NM_PLATFORM_MATCH_WITH_ADDRTYPE_LINKLOCAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE_NORMAL))
return TRUE;
_LOGD(LOGD_DEVICE,
"linklocal6: starting IPv6 with method '%s', but the device has no link-local addresses "
"configured. Wait.",
nm_device_get_effective_ip_config_method(self, AF_INET6));
check_and_add_ipv6ll_addr(self);
/* Depending on the network and what the 'dad_transmits' and 'retrans_time_ms'
* sysctl values are, DAD for the IPv6LL address may take quite a while.
* FIXME: use dad/retrans sysctl values if they are higher than a minimum time.
* (rh #1101809)
*/
priv->linklocal6_timeout_id = g_timeout_add_seconds(15, linklocal6_timeout_cb, self);
return FALSE;
}
/*****************************************************************************/
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, const NMIP4Config *config)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMDeviceMtuSource source = NM_DEVICE_MTU_SOURCE_NONE;
guint32 ip6_mtu, ip6_mtu_orig;
guint32 mtu_desired, mtu_desired_orig;
guint32 mtu_plat;
struct {
gboolean initialized;
guint32 value;
} ip6_mtu_sysctl = {
0,
};
int ifindex;
char sbuf[64], sbuf1[64], 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;
}
{
guint32 mtu = 0;
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 (config && !force && source < NM_DEVICE_MTU_SOURCE_IP_CONFIG
&& nm_ip4_config_get_mtu(config)) {
mtu = nm_ip4_config_get_mtu(config);
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,
mtu_source_to_str(source),
(guint) source,
mtu_source_to_str(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;
}
}
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) {
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);
#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 && 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, NM_DEVICE_GET_PRIVATE(self)->ip_config_4);
} else
_LOGT(LOGD_DEVICE,
"mtu: commit-mtu... skip due to state %s",
nm_device_state_to_str(state));
}
static void
ndisc_config_changed(NMNDisc *ndisc, const NMNDiscData *rdata, guint changed_int, NMDevice *self)
{
NMNDiscConfigMap changed = changed_int;
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
guint i;
g_return_if_fail(priv->act_request.obj);
if (!applied_config_get_current(&priv->ac_ip6_config))
applied_config_init_new(&priv->ac_ip6_config, self, AF_INET6);
if (changed & NM_NDISC_CONFIG_ADDRESSES) {
guint8 plen;
guint32 ifa_flags;
/* Check, whether kernel is recent enough to help user space handling RA.
* If it's not supported, we have no ipv6-privacy and must add autoconf
* addresses as /128. The reason for the /128 is to prevent the kernel
* from adding a prefix route for this address. */
ifa_flags = 0;
if (nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
ifa_flags |= IFA_F_NOPREFIXROUTE;
if (NM_IN_SET(priv->ndisc_use_tempaddr,
NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR))
ifa_flags |= IFA_F_MANAGETEMPADDR;
plen = 64;
} else
plen = 128;
nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.orig,
rdata->addresses,
rdata->addresses_n,
plen,
ifa_flags);
if (priv->ac_ip6_config.current) {
nm_ip6_config_reset_addresses_ndisc((NMIP6Config *) priv->ac_ip6_config.current,
rdata->addresses,
rdata->addresses_n,
plen,
ifa_flags);
}
}
if (NM_FLAGS_ANY(changed, NM_NDISC_CONFIG_ROUTES | NM_NDISC_CONFIG_GATEWAYS)) {
nm_ip6_config_reset_routes_ndisc(
(NMIP6Config *) priv->ac_ip6_config.orig,
rdata->gateways,
rdata->gateways_n,
rdata->routes,
rdata->routes_n,
nm_device_get_route_table(self, AF_INET6),
nm_device_get_route_metric(self, AF_INET6),
nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
if (priv->ac_ip6_config.current) {
nm_ip6_config_reset_routes_ndisc(
(NMIP6Config *) priv->ac_ip6_config.current,
rdata->gateways,
rdata->gateways_n,
rdata->routes,
rdata->routes_n,
nm_device_get_route_table(self, AF_INET6),
nm_device_get_route_metric(self, AF_INET6),
nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_RTA_PREF));
}
}
if (changed & NM_NDISC_CONFIG_DNS_SERVERS) {
/* Rebuild DNS server list from neighbor discovery cache. */
applied_config_reset_nameservers(&priv->ac_ip6_config);
for (i = 0; i < rdata->dns_servers_n; i++)
applied_config_add_nameserver(&priv->ac_ip6_config,
(const NMIPAddr *) &rdata->dns_servers[i].address);
}
if (changed & NM_NDISC_CONFIG_DNS_DOMAINS) {
/* Rebuild domain list from neighbor discovery cache. */
applied_config_reset_searches(&priv->ac_ip6_config);
for (i = 0; i < rdata->dns_domains_n; i++)
applied_config_add_search(&priv->ac_ip6_config, rdata->dns_domains[i].domain);
}
if (changed & NM_NDISC_CONFIG_DHCP_LEVEL) {
dhcp6_cleanup(self, CLEANUP_TYPE_DECONFIGURE, TRUE);
priv->dhcp6.mode = rdata->dhcp_level;
if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE) {
_LOGD(LOGD_DEVICE | LOGD_DHCP6,
"Activation: Stage 3 of 5 (IP Configure Start) starting DHCPv6"
" as requested by IPv6 router...");
if (!dhcp6_start(self, FALSE)) {
if (priv->dhcp6.mode == NM_NDISC_DHCP_LEVEL_MANAGED) {
nm_device_state_changed(self,
NM_DEVICE_STATE_FAILED,
NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
return;
}
}
}
}
if (changed & NM_NDISC_CONFIG_HOP_LIMIT)
nm_platform_sysctl_ip_conf_set_ipv6_hop_limit_safe(nm_device_get_platform(self),
nm_device_get_ip_iface(self),
rdata->hop_limit);
if (changed & NM_NDISC_CONFIG_REACHABLE_TIME) {
nm_platform_sysctl_ip_neigh_set_ipv6_reachable_time(nm_device_get_platform(self),
nm_device_get_ip_iface(self),
rdata->reachable_time_ms);
}
if (changed & NM_NDISC_CONFIG_RETRANS_TIMER) {
nm_platform_sysctl_ip_neigh_set_ipv6_retrans_time(nm_device_get_platform(self),
nm_device_get_ip_iface(self),
rdata->retrans_timer_ms);
}
if (changed & NM_NDISC_CONFIG_MTU) {
if (priv->ip6_mtu != rdata->mtu) {
_LOGD(LOGD_DEVICE, "mtu: set IPv6 MTU to %u", (guint) rdata->mtu);
priv->ip6_mtu = rdata->mtu;
}
}
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
}
static void
ndisc_ra_timeout(NMNDisc *ndisc, NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
/* We don't want to stop listening for router advertisements completely,
* but instead let device activation continue activating. If an RA
* shows up later, we'll use it as long as the device is not disconnected.
*/
_LOGD(LOGD_IP6, "timed out waiting for IPv6 router advertisement");
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
/* If RA is our only source of addressing information and we don't
* ever receive one, then time out IPv6. But if there is other
* IPv6 configuration, like manual IPv6 addresses or external IPv6
* config, consider that sufficient for IPv6 success.
*
* FIXME: it doesn't seem correct to determine this based on which
* addresses we find inside priv->ip_config_6.
*/
if (priv->ip_config_6
&& nm_ip6_config_find_first_address(priv->ip_config_6,
NM_PLATFORM_MATCH_WITH_ADDRTYPE_NORMAL
| NM_PLATFORM_MATCH_WITH_ADDRSTATE__ANY))
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
else
nm_device_activate_schedule_ip_config_timeout(self, AF_INET6);
}
}
static void
addrconf6_start_with_link_ready(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMUtilsIPv6IfaceId iid;
g_assert(priv->ndisc);
if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) {
_LOGD(LOGD_IP6, "addrconf6: using the device EUI-64 identifier");
nm_ndisc_set_iid(priv->ndisc, iid);
} else {
/* Don't abort the addrconf at this point -- if ndisc needs the iid
* it will notice this itself. */
_LOGI(LOGD_IP6, "addrconf6: no interface identifier; IPv6 address creation may fail");
}
/* Apply any manual configuration before starting RA */
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "failed to apply manual IPv6 configuration");
if (nm_ndisc_get_node_type(priv->ndisc) == NM_NDISC_NODE_TYPE_ROUTER) {
nm_device_sysctl_ip_conf_set(self, AF_INET6, "forwarding", "1");
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
priv->needs_ip6_subnet = TRUE;
g_signal_emit(self, signals[IP6_SUBNET_NEEDED], 0);
}
priv->ndisc_changed_id = g_signal_connect(priv->ndisc,
NM_NDISC_CONFIG_RECEIVED,
G_CALLBACK(ndisc_config_changed),
self);
priv->ndisc_timeout_id = g_signal_connect(priv->ndisc,
NM_NDISC_RA_TIMEOUT_SIGNAL,
G_CALLBACK(ndisc_ra_timeout),
self);
ndisc_set_router_config(priv->ndisc, self);
nm_ndisc_start(priv->ndisc);
priv->ndisc_started = TRUE;
return;
}
static gboolean
addrconf6_start(NMDevice *self, NMSettingIP6ConfigPrivacy use_tempaddr)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
NMSettingIP6Config *s_ip6 = NULL;
GError * error = NULL;
NMUtilsStableType stable_type;
const char * stable_id;
NMNDiscNodeType node_type;
int max_addresses;
int router_solicitations;
int router_solicitation_interval;
guint32 ra_timeout;
guint32 default_ra_timeout;
connection = nm_device_get_applied_connection(self);
g_assert(connection);
nm_assert(!applied_config_get_current(&priv->ac_ip6_config));
applied_config_clear(&priv->ac_ip6_config);
nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
nm_clear_g_source(&priv->rt6_temporary_not_available_id);
s_ip6 = NM_SETTING_IP6_CONFIG(nm_connection_get_setting_ip6_config(connection));
g_assert(s_ip6);
if (nm_streq(nm_device_get_effective_ip_config_method(self, AF_INET6),
NM_SETTING_IP4_CONFIG_METHOD_SHARED))
node_type = NM_NDISC_NODE_TYPE_ROUTER;
else
node_type = NM_NDISC_NODE_TYPE_HOST;
nm_lndp_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);
priv->ndisc = nm_lndp_ndisc_new(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
nm_device_get_ip_iface(self),
stable_type,
stable_id,
nm_setting_ip6_config_get_addr_gen_mode(s_ip6),
node_type,
max_addresses,
router_solicitations,
router_solicitation_interval,
ra_timeout,
&error);
if (!priv->ndisc) {
_LOGE(LOGD_IP6, "addrconf6: failed to start neighbor discovery: %s", error->message);
g_error_free(error);
return FALSE;
}
priv->ndisc_use_tempaddr = use_tempaddr;
if (NM_IN_SET(use_tempaddr,
NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR,
NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR)
&& !nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_EXTENDED_IFA_FLAGS)) {
_LOGW(LOGD_IP6,
"The kernel does not support extended IFA_FLAGS needed by NM for "
"IPv6 private addresses. This feature is not available");
}
/* ensure link local is ready... */
if (!linklocal6_start(self)) {
/* wait for the LL address to show up */
return TRUE;
}
/* already have the LL address; kick off neighbor discovery */
addrconf6_start_with_link_ready(self);
return TRUE;
}
static void
addrconf6_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->ndisc_started = FALSE;
nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_changed_id);
nm_clear_g_signal_handler(priv->ndisc, &priv->ndisc_timeout_id);
applied_config_clear(&priv->ac_ip6_config);
nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
nm_clear_g_source(&priv->rt6_temporary_not_available_id);
if (priv->ndisc) {
nm_ndisc_stop(priv->ndisc);
g_clear_object(&priv->ndisc);
}
}
/*****************************************************************************/
static void
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
restore_ip6_properties(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
GHashTableIter iter;
gpointer key, value;
g_hash_table_iter_init(&iter, priv->ip6_saved_properties);
while (g_hash_table_iter_next(&iter, &key, &value)) {
/* Don't touch "disable_ipv6" if we're doing userland IPv6LL */
if (priv->ipv6ll_handle && nm_streq(key, "disable_ipv6"))
continue;
nm_device_sysctl_ip_conf_set(self, AF_INET6, key, value);
}
}
static void
set_disable_ipv6(NMDevice *self, const char *value)
{
/* We only touch disable_ipv6 when NM is not managing the IPv6LL address */
if (!NM_DEVICE_GET_PRIVATE(self)->ipv6ll_handle)
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", value);
}
static void
set_nm_ipv6ll(NMDevice *self, gboolean enable)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int ifindex = nm_device_get_ip_ifindex(self);
if (!nm_platform_kernel_support_get(NM_PLATFORM_KERNEL_SUPPORT_TYPE_USER_IPV6LL))
return;
priv->ipv6ll_handle = enable;
if (ifindex > 0) {
const char *detail = enable ? "enable" : "disable";
int r;
_LOGD(LOGD_IP6, "will %s userland IPv6LL", detail);
r = nm_platform_link_set_user_ipv6ll_enabled(nm_device_get_platform(self), ifindex, enable);
if (r < 0) {
_NMLOG(NM_IN_SET(r, -NME_PL_NOT_FOUND, -NME_PL_OPNOTSUPP) ? LOGL_DEBUG : LOGL_WARN,
LOGD_IP6,
"failed to %s userspace IPv6LL address handling (%s)",
detail,
nm_strerror(r));
}
if (enable) {
gs_free char *value = NULL;
/* Bounce IPv6 to ensure the kernel stops IPv6LL address generation */
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");
/* Ensure IPv6 is enabled */
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 NMActStageReturn
act_stage3_ip_config_start(NMDevice * self,
int addr_family,
gpointer * out_config,
NMDeviceStateReason *out_failure_reason)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
NMActStageReturn ret = NM_ACT_STAGE_RETURN_FAILURE;
const char * method;
nm_assert_addr_family(addr_family);
connection = nm_device_get_applied_connection(self);
g_return_val_if_fail(connection, NM_ACT_STAGE_RETURN_FAILURE);
if (connection_ip_method_requires_carrier(connection, addr_family, NULL)
&& nm_device_is_master(self) && !priv->carrier) {
_LOGI(LOGD_IP | LOGD_DEVICE,
"IPv%c config waiting until carrier is on",
nm_utils_addr_family_to_char(addr_family));
return NM_ACT_STAGE_RETURN_IP_WAIT;
}
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)) {
_LOGI(LOGD_DEVICE | LOGD_IP,
"IPv%c config waiting until slaves are ready",
nm_utils_addr_family_to_char(addr_family));
return NM_ACT_STAGE_RETURN_IP_WAIT;
}
}
if (!IS_IPv4)
priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_NONE;
method = nm_device_get_effective_ip_config_method(self, addr_family);
_LOGD(LOGD_IP | LOGD_DEVICE,
"IPv%c config method is %s",
nm_utils_addr_family_to_char(addr_family),
method);
if (IS_IPv4) {
if (NM_IN_STRSET(method,
NM_SETTING_IP4_CONFIG_METHOD_AUTO,
NM_SETTING_IP4_CONFIG_METHOD_MANUAL)) {
NMSettingIPConfig *s_ip4;
NMIP4Config ** configs, *config;
guint num_addresses;
s_ip4 = nm_connection_get_setting_ip4_config(connection);
g_return_val_if_fail(s_ip4, NM_ACT_STAGE_RETURN_FAILURE);
num_addresses = nm_setting_ip_config_get_num_addresses(s_ip4);
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
ret = dhcp4_start(self);
if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_DHCP_START_FAILED);
return ret;
}
} else {
g_return_val_if_fail(num_addresses != 0, NM_ACT_STAGE_RETURN_FAILURE);
ret = NM_ACT_STAGE_RETURN_POSTPONE;
}
if (num_addresses) {
config = nm_device_ip4_config_new(self);
nm_ip4_config_merge_setting(config,
nm_connection_get_setting_ip4_config(connection),
NM_SETTING_CONNECTION_MDNS_DEFAULT,
NM_SETTING_CONNECTION_LLMNR_DEFAULT,
nm_device_get_route_table(self, AF_INET),
nm_device_get_route_metric(self, AF_INET));
configs = g_new0(NMIP4Config *, 2);
configs[0] = config;
ipv4_dad_start(self, configs, ipv4_manual_method_apply);
}
} else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_LINK_LOCAL)) {
ret = ipv4ll_start(self);
if (ret == NM_ACT_STAGE_RETURN_FAILURE)
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_AUTOIP_START_FAILED);
} else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
if (out_config) {
*out_config = shared4_new_config(self, connection);
if (*out_config) {
priv->dnsmasq_manager = nm_dnsmasq_manager_new(nm_device_get_ip_iface(self));
ret = NM_ACT_STAGE_RETURN_SUCCESS;
} else {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
ret = NM_ACT_STAGE_RETURN_FAILURE;
}
} else
g_return_val_if_reached(NM_ACT_STAGE_RETURN_FAILURE);
} else if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_DISABLED))
ret = NM_ACT_STAGE_RETURN_SUCCESS;
else
_LOGW(LOGD_IP4, "unhandled IPv4 config method '%s'; will fail", method);
return ret;
} else {
NMSettingIP6ConfigPrivacy ip6_privacy = NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN;
const char * ip6_privacy_str = "0";
NMPlatform * platform;
int ifindex;
if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DISABLED)) {
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
return NM_ACT_STAGE_RETURN_IP_DONE;
}
if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_IGNORE)) {
if (!nm_device_sys_iface_state_is_external(self)) {
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.
*/
ifindex = nm_device_get_ip_ifindex(self);
platform = nm_device_get_platform(self);
if (ifindex > 0) {
gs_unref_object NMIP6Config *config = nm_device_ip6_config_new(self);
nm_platform_ip_route_flush(platform, AF_INET6, ifindex);
nm_platform_ip_address_flush(platform, AF_INET6, ifindex);
nm_device_set_ip_config(self, AF_INET6, (NMIPConfig *) config, FALSE, NULL);
}
} else {
gboolean ipv6ll_handle_old = priv->ipv6ll_handle;
/* 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.
*/
set_nm_ipv6ll(self, FALSE);
if (ipv6ll_handle_old)
nm_device_sysctl_ip_conf_set(self, AF_INET6, "disable_ipv6", "1");
restore_ip6_properties(self);
}
}
return NM_ACT_STAGE_RETURN_IP_DONE;
}
/* 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, priv->ip_config_4);
/* 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))
set_nm_ipv6ll(self, TRUE);
/* Re-enable IPv6 on the interface */
nm_device_sysctl_ip_conf_set(self, AF_INET6, "accept_ra", "0");
set_disable_ipv6(self, "0");
/* Synchronize external IPv6 configuration with kernel, since
* linklocal6_start() uses the information there to determine if we can
* proceed with the selected method (SLAAC, DHCP, link-local).
*/
nm_platform_process_events(nm_device_get_platform(self));
g_clear_object(&priv->ext_ip6_config_captured);
priv->ext_ip6_config_captured =
nm_ip6_config_capture(nm_device_get_multi_index(self),
nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
ip6_privacy = _prop_get_ipv6_ip6_privacy(self);
if (NM_IN_STRSET(method,
NM_SETTING_IP6_CONFIG_METHOD_AUTO,
NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
if (!addrconf6_start(self, ip6_privacy)) {
/* IPv6 might be disabled; allow IPv4 to proceed */
ret = NM_ACT_STAGE_RETURN_IP_FAIL;
} else
ret = NM_ACT_STAGE_RETURN_POSTPONE;
} else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_LINK_LOCAL)) {
ret =
linklocal6_start(self) ? NM_ACT_STAGE_RETURN_SUCCESS : NM_ACT_STAGE_RETURN_POSTPONE;
} else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_DHCP)) {
priv->dhcp6.mode = NM_NDISC_DHCP_LEVEL_MANAGED;
if (!dhcp6_start(self, TRUE)) {
/* IPv6 might be disabled; allow IPv4 to proceed */
ret = NM_ACT_STAGE_RETURN_IP_FAIL;
} else
ret = NM_ACT_STAGE_RETURN_POSTPONE;
} else if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_MANUAL))
ret = NM_ACT_STAGE_RETURN_SUCCESS;
else
_LOGW(LOGD_IP6, "unhandled IPv6 config method '%s'; will fail", method);
if (ret != NM_ACT_STAGE_RETURN_FAILURE
&& !nm_device_sys_iface_state_is_external_or_assume(self)) {
switch (ip6_privacy) {
case NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN:
case NM_SETTING_IP6_CONFIG_PRIVACY_DISABLED:
ip6_privacy_str = "0";
break;
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_PUBLIC_ADDR:
ip6_privacy_str = "1";
break;
case NM_SETTING_IP6_CONFIG_PRIVACY_PREFER_TEMP_ADDR:
ip6_privacy_str = "2";
break;
}
nm_device_sysctl_ip_conf_set(self, AF_INET6, "use_tempaddr", ip6_privacy_str);
}
return ret;
}
}
gboolean
nm_device_activate_stage3_ip_start(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMActStageReturn ret;
NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
gs_unref_object NMIPConfig *ip_config = NULL;
g_assert(priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_WAIT);
if (nm_device_sys_iface_state_is_external(self)) {
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
check_ip_state(self, FALSE, TRUE);
return TRUE;
}
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_CONF);
ret = NM_DEVICE_GET_CLASS(self)->act_stage3_ip_config_start(self,
addr_family,
(gpointer *) &ip_config,
&failure_reason);
switch (ret) {
case NM_ACT_STAGE_RETURN_SUCCESS:
if (!IS_IPv4) {
/* Here we get a static IPv6 config, like for Shared where it's
* autogenerated or from modems where it comes from ModemManager.
*/
if (!ip_config)
ip_config = nm_device_ip_config_new(self, addr_family);
nm_assert(!applied_config_get_current(&priv->ac_ip6_config));
applied_config_init(&priv->ac_ip6_config, ip_config);
ip_config = NULL;
}
nm_device_activate_schedule_ip_config_result(self, addr_family, ip_config);
break;
case NM_ACT_STAGE_RETURN_IP_DONE:
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
check_ip_state(self, FALSE, TRUE);
break;
case NM_ACT_STAGE_RETURN_FAILURE:
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
return FALSE;
case NM_ACT_STAGE_RETURN_IP_FAIL:
/* Activation not wanted */
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
break;
case NM_ACT_STAGE_RETURN_IP_WAIT:
/* Wait for something to try IP config again */
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
break;
default:
g_assert(ret == NM_ACT_STAGE_RETURN_POSTPONE);
}
return TRUE;
}
/*
* activate_stage3_ip_config_start
*
* Begin automatic/manual IP configuration
*
*/
static void
activate_stage3_ip_config_start(NMDevice *self)
{
int ifindex;
_set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_WAIT);
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_WAIT);
_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 ((ifindex = nm_device_get_ip_ifindex(self)) > 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));
if (nm_device_activate_ip4_state_in_wait(self)
&& !nm_device_activate_stage3_ip_start(self, AF_INET))
return;
if (nm_device_activate_ip6_state_in_wait(self)
&& !nm_device_activate_stage3_ip_start(self, AF_INET6))
return;
/* Proxy */
nm_device_set_proxy_config(self, NULL);
check_ip_state(self, TRUE, TRUE);
}
static void
fw_change_zone_cb(NMFirewallManager * firewall_manager,
NMFirewallManagerCallId *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_start(self);
break;
case FIREWALL_STATE_WAIT_IP_CONFIG:
priv->fw_state = FIREWALL_STATE_INITIALIZED;
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
|| priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE)
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_firewall_manager_cancel_call(priv->fw_call);
nm_assert(!priv->fw_call);
}
if (G_UNLIKELY(!priv->fw_mgr))
priv->fw_mgr = g_object_ref(nm_firewall_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_firewall_manager_add_or_change_zone(priv->fw_mgr,
nm_device_get_ip_iface(self),
zone,
FALSE, /* change zone */
fw_change_zone_cb,
self);
}
/*
* nm_device_activate_schedule_stage3_ip_config_start
*
* Schedule IP configuration start
*/
void
nm_device_activate_schedule_stage3_ip_config_start(NMDevice *self)
{
NMDevicePrivate *priv;
int ifindex;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_if_fail(priv->act_request.obj);
ifindex = nm_device_get_ip_ifindex(self);
/* Add the interface to the specified firewall zone */
if (priv->fw_state == 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 */
} else if (priv->fw_state == FIREWALL_STATE_WAIT_STAGE_3) {
/* a firewall call for stage3 is pending. Return and wait. */
return;
}
nm_assert(ifindex <= 0 || priv->fw_state == FIREWALL_STATE_INITIALIZED);
activation_source_schedule(self, activate_stage3_ip_config_start, AF_INET);
}
static NMActStageReturn
act_stage4_ip_config_timeout(NMDevice * self,
int addr_family,
NMDeviceStateReason *out_failure_reason)
{
nm_assert_addr_family(addr_family);
if (!get_ip_config_may_fail(self, addr_family)) {
NM_SET_OUT(out_failure_reason, NM_DEVICE_STATE_REASON_IP_CONFIG_UNAVAILABLE);
return NM_ACT_STAGE_RETURN_FAILURE;
}
return NM_ACT_STAGE_RETURN_SUCCESS;
}
static void
activate_stage4_ip_config_timeout_x(NMDevice *self, int addr_family)
{
NMDeviceStateReason failure_reason = NM_DEVICE_STATE_REASON_NONE;
NMActStageReturn ret;
ret =
NM_DEVICE_GET_CLASS(self)->act_stage4_ip_config_timeout(self, addr_family, &failure_reason);
if (ret == NM_ACT_STAGE_RETURN_POSTPONE)
return;
if (ret == NM_ACT_STAGE_RETURN_FAILURE) {
nm_device_state_changed(self, NM_DEVICE_STATE_FAILED, failure_reason);
return;
}
g_assert(ret == NM_ACT_STAGE_RETURN_SUCCESS);
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_FAIL);
check_ip_state(self, FALSE, TRUE);
}
static void
activate_stage4_ip_config_timeout_4(NMDevice *self)
{
activate_stage4_ip_config_timeout_x(self, AF_INET);
}
static void
activate_stage4_ip_config_timeout_6(NMDevice *self)
{
activate_stage4_ip_config_timeout_x(self, AF_INET6);
}
void
nm_device_activate_schedule_ip_config_timeout(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
priv = NM_DEVICE_GET_PRIVATE(self);
g_return_if_fail(priv->act_request.obj);
activation_source_schedule(self,
IS_IPv4 ? activate_stage4_ip_config_timeout_4
: activate_stage4_ip_config_timeout_6,
addr_family);
}
static gboolean
share_init(NMDevice *self, GError **error)
{
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"};
guint i;
int errsv;
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;
_LOGD(LOGD_SHARING,
"share: error enabling IPv4 forwarding: (%d) %s",
errsv,
nm_strerror_native(errsv));
g_set_error(error,
NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"cannot set ipv4/ip_forward: %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(LOGD_SHARING,
"share: error enabling dynamic addresses: (%d) %s",
errsv,
nm_strerror_native(errsv));
}
for (i = 0; i < G_N_ELEMENTS(modules); i++)
nm_utils_modprobe(NULL, FALSE, modules[i], NULL);
return TRUE;
}
static gboolean
start_sharing(NMDevice *self, NMIP4Config *config, GError **error)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMActRequest * req;
const NMPlatformIP4Address *ip4_addr = NULL;
const char * ip_iface;
GError * local = NULL;
NMConnection * conn;
NMSettingConnection * s_con;
gboolean announce_android_metered;
NMUtilsShareRules * share_rules;
g_return_val_if_fail(config, FALSE);
ip_iface = nm_device_get_ip_iface(self);
if (!ip_iface) {
g_set_error(error, NM_UTILS_ERROR, NM_UTILS_ERROR_UNKNOWN, "device has no ip interface");
return FALSE;
}
ip4_addr = nm_ip4_config_get_first_address(config);
if (!ip4_addr || !ip4_addr->address) {
g_set_error(error,
NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"could not determine IPv4 address");
return FALSE;
}
if (!share_init(self, error))
return FALSE;
req = nm_device_get_act_request(self);
g_return_val_if_fail(req, FALSE);
share_rules = nm_utils_share_rules_new();
nm_utils_share_rules_add_all_rules(share_rules, ip_iface, ip4_addr->address, ip4_addr->plen);
nm_utils_share_rules_apply(share_rules, TRUE);
nm_act_request_set_shared(req, share_rules);
conn = nm_act_request_get_applied_connection(req);
s_con = nm_connection_get_setting_connection(conn);
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;
}
if (!nm_dnsmasq_manager_start(priv->dnsmasq_manager,
config,
announce_android_metered,
&local)) {
g_set_error(error,
NM_UTILS_ERROR,
NM_UTILS_ERROR_UNKNOWN,
"could not start dnsmasq due to %s",
local->message);
g_error_free(local);
nm_act_request_set_shared(req, NULL);
return FALSE;
}
priv->dnsmasq_state_id = g_signal_connect(priv->dnsmasq_manager,
NM_DNS_MASQ_MANAGER_STATE_CHANGED,
G_CALLBACK(dnsmasq_state_changed_cb),
self);
return TRUE;
}
static void
arp_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_clear_pointer(&priv->acd.announcing, nm_acd_manager_free);
}
void
nm_device_arp_announce(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
NMConnection * connection;
NMSettingIPConfig *s_ip4;
guint num, i;
const guint8 * hw_addr;
size_t hw_addr_len = 0;
arp_cleanup(self);
hw_addr = nm_platform_link_get_address(nm_device_get_platform(self),
nm_device_get_ip_ifindex(self),
&hw_addr_len);
if (!hw_addr || hw_addr_len != ETH_ALEN)
return;
/* We only care about manually-configured addresses; DHCP- and autoip-configured
* ones should already have been seen on the network at this point.
*/
connection = nm_device_get_applied_connection(self);
if (!connection)
return;
s_ip4 = nm_connection_get_setting_ip4_config(connection);
if (!s_ip4)
return;
num = nm_setting_ip_config_get_num_addresses(s_ip4);
if (num == 0)
return;
priv->acd.announcing =
nm_acd_manager_new(nm_device_get_ip_ifindex(self), hw_addr, hw_addr_len, NULL, NULL);
for (i = 0; i < num; i++) {
NMIPAddress *ip = nm_setting_ip_config_get_address(s_ip4, i);
in_addr_t addr;
if (inet_pton(AF_INET, nm_ip_address_get_address(ip), &addr) == 1)
nm_acd_manager_add_address(priv->acd.announcing, addr);
else
g_warn_if_reached();
}
nm_acd_manager_announce_addresses(priv->acd.announcing);
}
static void
activate_stage5_ip_config_result_x(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMActRequest * req;
const char * method;
int ip_ifindex;
int errsv;
gboolean do_announce = FALSE;
req = nm_device_get_act_request(self);
g_assert(req);
/* Interface must be IFF_UP before IP config can be applied */
ip_ifindex = nm_device_get_ip_ifindex(self);
g_return_if_fail(ip_ifindex);
if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex)
&& !nm_device_sys_iface_state_is_external_or_assume(self)) {
nm_platform_link_set_up(nm_device_get_platform(self), ip_ifindex, NULL);
if (!nm_platform_link_is_up(nm_device_get_platform(self), ip_ifindex))
_LOGW(LOGD_DEVICE,
"interface %s not up for IP configuration",
nm_device_get_ip_iface(self));
}
if (!ip_config_merge_and_apply(self, addr_family, TRUE)) {
_LOGD(LOGD_DEVICE | LOGD_IPX(IS_IPv4),
"Activation: Stage 5 of 5 (IPv%c Commit) failed",
nm_utils_addr_family_to_char(addr_family));
nm_device_ip_method_failed(self, addr_family, NM_DEVICE_STATE_REASON_CONFIG_FAILED);
return;
}
if (!IS_IPv4) {
if (priv->dhcp6.mode != NM_NDISC_DHCP_LEVEL_NONE
&& priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF) {
if (applied_config_get_current(&priv->dhcp6.ip6_config)) {
/* If IPv6 wasn't the first IP to complete, and DHCP was used,
* then ensure dispatcher scripts get the DHCP lease information.
*/
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP6_CHANGE,
self,
NULL,
NULL,
NULL,
NULL);
} else {
/* still waiting for first dhcp6 lease. */
return;
}
}
}
/* Start IPv4 sharing/IPv6 forwarding if we need it */
method = nm_device_get_effective_ip_config_method(self, addr_family);
if (IS_IPv4) {
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_SHARED)) {
gs_free_error GError *error = NULL;
if (!start_sharing(self, priv->ip_config_4, &error)) {
_LOGW(LOGD_SHARING,
"Activation: Stage 5 of 5 (IPv4 Commit) start sharing failed: %s",
error->message);
nm_device_ip_method_failed(self,
AF_INET,
NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
return;
}
}
} else {
if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_SHARED)) {
if (!nm_platform_sysctl_set(
nm_device_get_platform(self),
NMP_SYSCTL_PATHID_ABSOLUTE("/proc/sys/net/ipv6/conf/all/forwarding"),
"1")) {
errsv = errno;
_LOGE(LOGD_SHARING,
"share: error enabling IPv6 forwarding: (%d) %s",
errsv,
nm_strerror_native(errsv));
nm_device_ip_method_failed(self,
AF_INET6,
NM_DEVICE_STATE_REASON_SHARED_START_FAILED);
return;
}
}
}
if (IS_IPv4) {
if (priv->dhcp_data_4.client) {
gs_free_error GError *error = NULL;
if (!nm_dhcp_client_accept(priv->dhcp_data_4.client, &error)) {
_LOGW(LOGD_DHCP4,
"Activation: Stage 5 of 5 (IPv4 Commit) error accepting lease: %s",
error->message);
nm_device_ip_method_failed(self, AF_INET, NM_DEVICE_STATE_REASON_DHCP_ERROR);
return;
}
}
/* If IPv4 wasn't the first to complete, and DHCP was used, then ensure
* dispatcher scripts get the DHCP lease information.
*/
if (priv->dhcp_data_4.client && nm_device_activate_ip4_state_in_conf(self)
&& (nm_device_get_state(self) > NM_DEVICE_STATE_IP_CONFIG)) {
nm_dispatcher_call_device(NM_DISPATCHER_ACTION_DHCP4_CHANGE,
self,
NULL,
NULL,
NULL,
NULL);
}
}
if (!IS_IPv4) {
/* Check if we have to wait for DAD */
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && !priv->dad6_ip6_config) {
if (!priv->carrier && priv->ignore_carrier && get_ip_config_may_fail(self, AF_INET6))
_LOGI(LOGD_DEVICE | LOGD_IP6,
"IPv6 DAD: carrier missing and ignored, not delaying activation");
else
priv->dad6_ip6_config = dad6_get_pending_addresses(self);
if (priv->dad6_ip6_config) {
_LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD: awaiting termination");
} else {
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_DONE);
check_ip_state(self, FALSE, TRUE);
}
}
}
if (IS_IPv4) {
/* Send ARP announcements */
if (nm_device_is_master(self)) {
CList * iter;
SlaveInfo *info;
/* Skip announcement if there are no device enslaved, for two reasons:
* 1) the master has a temporary MAC address until the first slave comes
* 2) announcements are going to be dropped anyway without slaves
*/
do_announce = FALSE;
c_list_for_each (iter, &priv->slaves) {
info = c_list_entry(iter, SlaveInfo, lst_slave);
if (info->slave_is_enslaved) {
do_announce = TRUE;
break;
}
}
} else
do_announce = TRUE;
if (do_announce)
nm_device_arp_announce(self);
}
if (IS_IPv4) {
/* Enter the IP_CHECK state if this is the first method to complete */
_set_ip_state(self, AF_INET, NM_DEVICE_IP_STATE_DONE);
check_ip_state(self, FALSE, TRUE);
}
}
static void
activate_stage5_ip_config_result_4(NMDevice *self)
{
activate_stage5_ip_config_result_x(self, AF_INET);
}
static void
activate_stage5_ip_config_result_6(NMDevice *self)
{
activate_stage5_ip_config_result_x(self, AF_INET6);
}
#define activate_stage5_ip_config_result_x_fcn(addr_family) \
(NM_IS_IPv4(addr_family) ? activate_stage5_ip_config_result_4 \
: activate_stage5_ip_config_result_6)
void
nm_device_activate_schedule_ip_config_result(NMDevice *self, int addr_family, NMIPConfig *config)
{
NMDevicePrivate *priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(!config || (IS_IPv4 && nm_ip_config_get_addr_family(config) == AF_INET));
priv = NM_DEVICE_GET_PRIVATE(self);
if (IS_IPv4) {
applied_config_init(&priv->dev_ip_config_4, config);
} else {
/* If IP had previously failed, move it back to NM_DEVICE_IP_STATE_CONF since we
* clearly now have configuration.
*/
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_FAIL)
_set_ip_state(self, AF_INET6, NM_DEVICE_IP_STATE_CONF);
}
activation_source_schedule(self,
activate_stage5_ip_config_result_x_fcn(addr_family),
addr_family);
}
NMDeviceIPState
nm_device_activate_get_ip_state(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_val_if_fail(NM_IS_DEVICE(self), NM_DEVICE_IP_STATE_NONE);
g_return_val_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6), NM_DEVICE_IP_STATE_NONE);
return NM_DEVICE_GET_PRIVATE(self)->ip_state_x[IS_IPv4];
}
static void
dad6_add_pending_address(NMDevice * self,
NMPlatform * platform,
int ifindex,
const struct in6_addr *address,
NMIP6Config ** dad6_config)
{
const NMPlatformIP6Address *pl_addr;
pl_addr = nm_platform_ip6_address_get(platform, ifindex, address);
if (pl_addr && NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_TENTATIVE)
&& !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_DADFAILED)
&& !NM_FLAGS_HAS(pl_addr->n_ifa_flags, IFA_F_OPTIMISTIC)) {
_LOGt(LOGD_DEVICE,
"IPv6 DAD: pending address %s",
nm_platform_ip6_address_to_string(pl_addr, NULL, 0));
if (!*dad6_config)
*dad6_config = nm_device_ip6_config_new(self);
nm_ip6_config_add_address(*dad6_config, pl_addr);
}
}
/*
* Returns a NMIP6Config containing NM-configured addresses which
* have the tentative flag, or NULL if none is present.
*/
static NMIP6Config *
dad6_get_pending_addresses(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMIP6Config * confs[] = {(NMIP6Config *) applied_config_get_current(&priv->ac_ip6_config),
(NMIP6Config *) applied_config_get_current(&priv->dhcp6.ip6_config),
priv->con_ip_config_6,
(NMIP6Config *) applied_config_get_current(&priv->dev2_ip_config_6)};
const NMPlatformIP6Address *addr;
NMIP6Config * dad6_config = NULL;
NMDedupMultiIter ipconf_iter;
guint i;
int ifindex;
NMPlatform * platform;
ifindex = nm_device_get_ip_ifindex(self);
g_return_val_if_fail(ifindex > 0, NULL);
platform = nm_device_get_platform(self);
if (priv->ipv6ll_has) {
dad6_add_pending_address(self, platform, ifindex, &priv->ipv6ll_addr, &dad6_config);
}
/* We are interested only in addresses that we have explicitly configured,
* not in externally added ones.
*/
for (i = 0; i < G_N_ELEMENTS(confs); i++) {
if (!confs[i])
continue;
nm_ip_config_iter_ip6_address_for_each (&ipconf_iter, confs[i], &addr) {
dad6_add_pending_address(self, platform, ifindex, &addr->address, &dad6_config);
}
}
return dad6_config;
}
/*****************************************************************************/
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;
}
}
}
static void
dnsmasq_cleanup(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->dnsmasq_manager)
return;
nm_clear_g_signal_handler(priv->dnsmasq_manager, &priv->dnsmasq_state_id);
nm_dnsmasq_manager_stop(priv->dnsmasq_manager);
g_object_unref(priv->dnsmasq_manager);
priv->dnsmasq_manager = NULL;
}
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;
NMDevice * self = data->device;
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: cleanup and delete virtual link #%d (id=%u)",
data->ifindex,
data->idle_add_id);
if (data->device) {
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(data->device);
gs_free_error GError *error = NULL;
g_object_remove_weak_pointer(G_OBJECT(data->device), (void **) &data->device);
priv->delete_on_deactivate_data = NULL;
if (!nm_device_unrealize(data->device, TRUE, &error))
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: unrealizing %d failed (%s)",
data->ifindex,
error->message);
} else if (data->ifindex > 0)
nm_platform_link_delete(nm_device_get_platform(self), data->ifindex);
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);
g_object_remove_weak_pointer(G_OBJECT(self), (void **) &data->device);
_LOGD(LOGD_DEVICE,
"delete_on_deactivate: cancel cleanup and delete virtual link #%d (id=%u)",
data->ifindex,
data->idle_add_id);
g_free(data);
}
}
static void
delete_on_deactivate_check_and_schedule(NMDevice *self, int ifindex)
{
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);
g_object_add_weak_pointer(G_OBJECT(self), (void **) &data->device);
data->device = self;
data->ifindex = ifindex;
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 #%d (id=%u)",
ifindex,
data->idle_add_id);
}
static void
_cleanup_ip_pre(NMDevice *self, int addr_family, CleanupType cleanup_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_NONE);
if (nm_clear_g_source(&priv->queued_ip_config_id_x[IS_IPv4])) {
_LOGD(LOGD_DEVICE,
"clearing queued IP%c config change",
nm_utils_addr_family_to_char(addr_family));
}
if (IS_IPv4) {
dhcp4_cleanup(self, cleanup_type, FALSE);
arp_cleanup(self);
dnsmasq_cleanup(self);
ipv4ll_cleanup(self);
g_slist_free_full(priv->acd.dad_list, (GDestroyNotify) nm_acd_manager_free);
priv->acd.dad_list = NULL;
} else {
g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
priv->dad6_failed_addrs = NULL;
g_clear_object(&priv->dad6_ip6_config);
dhcp6_cleanup(self, cleanup_type, FALSE);
nm_clear_g_source(&priv->linklocal6_timeout_id);
addrconf6_cleanup(self);
}
}
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_utils_strv_find_first((char **) 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;
}
void
nm_device_reactivate_ip_config(NMDevice * self,
int addr_family,
NMSettingIPConfig *s_ip_old,
NMSettingIPConfig *s_ip_new)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMDevicePrivate *priv;
const char * method_old;
const char * method_new;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->ip_state_x[IS_IPv4] == NM_DEVICE_IP_STATE_NONE)
return;
g_clear_object(&priv->con_ip_config_x[IS_IPv4]);
g_clear_object(&priv->ext_ip_config_x[IS_IPv4]);
if (IS_IPv4) {
g_clear_object(&priv->dev_ip_config_4.current);
} else {
g_clear_object(&priv->ac_ip6_config.current);
g_clear_object(&priv->dhcp6.ip6_config.current);
}
g_clear_object(&priv->dev2_ip_config_x[IS_IPv4].current);
if (!IS_IPv4) {
if (priv->ipv6ll_handle && !IN6_IS_ADDR_UNSPECIFIED(&priv->ipv6ll_addr))
priv->ipv6ll_has = TRUE;
}
priv->con_ip_config_x[IS_IPv4] = nm_device_ip_config_new(self, addr_family);
if (IS_IPv4) {
nm_ip4_config_merge_setting(priv->con_ip_config_4,
s_ip_new,
_prop_get_connection_mdns(self),
_prop_get_connection_llmnr(self),
nm_device_get_route_table(self, AF_INET),
nm_device_get_route_metric(self, AF_INET));
} else {
nm_ip6_config_merge_setting(priv->con_ip_config_6,
s_ip_new,
nm_device_get_route_table(self, AF_INET6),
nm_device_get_route_metric(self, AF_INET6));
}
method_old = (s_ip_old ? nm_setting_ip_config_get_method(s_ip_old) : NULL)
?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
: NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
method_new = (s_ip_new ? nm_setting_ip_config_get_method(s_ip_new) : NULL)
?: (IS_IPv4 ? NM_SETTING_IP4_CONFIG_METHOD_DISABLED
: NM_SETTING_IP6_CONFIG_METHOD_IGNORE);
if (!nm_streq0(method_old, method_new)) {
_cleanup_ip_pre(self, addr_family, CLEANUP_TYPE_DECONFIGURE);
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_WAIT);
if (!nm_device_activate_stage3_ip_start(self, addr_family)) {
_LOGW(LOGD_IP4,
"Failed to apply IPv%c configuration",
nm_utils_addr_family_to_char(addr_family));
}
return;
}
if (s_ip_old && s_ip_new) {
gint64 metric_old, metric_new;
/* For dynamic IP methods (DHCP, IPv4LL, WWAN) the route metric is
* set at activation/renewal time using the value from static
* configuration. To support runtime change we need to update the
* dynamic configuration in place and tell the DHCP client the new
* value to use for future renewals.
*/
metric_old = nm_setting_ip_config_get_route_metric(s_ip_old);
metric_new = nm_setting_ip_config_get_route_metric(s_ip_new);
if (metric_old != metric_new) {
if (IS_IPv4) {
if (priv->dev_ip_config_4.orig) {
nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev_ip_config_4.orig,
nm_device_get_route_metric(self, AF_INET));
}
if (priv->dev2_ip_config_4.orig) {
nm_ip4_config_update_routes_metric((NMIP4Config *) priv->dev2_ip_config_4.orig,
nm_device_get_route_metric(self, AF_INET));
}
if (priv->dhcp_data_4.client) {
nm_dhcp_client_set_route_metric(priv->dhcp_data_4.client,
nm_device_get_route_metric(self, AF_INET));
}
} else {
if (priv->ac_ip6_config.orig) {
nm_ip6_config_update_routes_metric((NMIP6Config *) priv->ac_ip6_config.orig,
nm_device_get_route_metric(self, AF_INET6));
}
if (priv->dhcp6.ip6_config.orig) {
nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dhcp6.ip6_config.orig,
nm_device_get_route_metric(self, AF_INET6));
}
if (priv->dev2_ip_config_6.orig) {
nm_ip6_config_update_routes_metric((NMIP6Config *) priv->dev2_ip_config_6.orig,
nm_device_get_route_metric(self, AF_INET6));
}
if (priv->dhcp_data_6.client) {
nm_dhcp_client_set_route_metric(priv->dhcp_data_6.client,
nm_device_get_route_metric(self, AF_INET6));
}
}
}
}
if (nm_device_get_ip_ifindex(self) > 0 && !ip_config_merge_and_apply(self, addr_family, TRUE)) {
_LOGW(LOGD_IPX(IS_IPv4),
"Failed to reapply IPv%c configuration",
nm_utils_addr_family_to_char(addr_family));
}
}
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(),
priv->proxy_config,
nm_device_get_ip_iface(self),
NULL,
NULL);
}
static void
reactivate_proxy_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->pacrunner_conf_id)
return;
nm_device_set_proxy_config(self, priv->dhcp4.pac_url);
_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);
}
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, *con_new;
NMSettingIPConfig * s_ip4_old, *s_ip4_new;
NMSettingIPConfig * s_ip6_old, *s_ip6_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_commit_first_time = TRUE;
priv->v6_commit_first_time = TRUE;
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = 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_init(self, FALSE);
if (priv->state >= NM_DEVICE_STATE_IP_CONFIG) {
s_ip4_old = nm_connection_get_setting_ip4_config(con_old);
s_ip4_new = nm_connection_get_setting_ip4_config(con_new);
s_ip6_old = nm_connection_get_setting_ip6_config(con_old);
s_ip6_new = nm_connection_get_setting_ip6_config(con_new);
/* Allow reapply of MTU */
priv->mtu_source = NM_DEVICE_MTU_SOURCE_NONE;
nm_device_reactivate_ip_config(self, AF_INET, s_ip4_old, s_ip4_new);
nm_device_reactivate_ip_config(self, AF_INET6, s_ip6_old, s_ip6_new);
_routing_rules_sync(self, NM_TERNARY_TRUE);
reactivate_proxy_config(self);
}
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, local);
local = NULL;
} else {
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_NO_SECRETS);
if (!var_settings)
var_settings = g_variant_new_array(G_VARIANT_TYPE("{sa{sv}}"), NULL, 0);
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)));
}
/*****************************************************************************/
typedef struct {
gint64 timestamp_ms;
bool dirty;
} IP6RoutesTemporaryNotAvailableData;
static gboolean
_rt6_temporary_not_available_timeout(gpointer user_data)
{
NMDevice * self = NM_DEVICE(user_data);
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->rt6_temporary_not_available_id = 0;
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
return G_SOURCE_REMOVE;
}
static gboolean
_rt6_temporary_not_available_set(NMDevice *self, GPtrArray *temporary_not_available)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
IP6RoutesTemporaryNotAvailableData *data;
GHashTableIter iter;
gint64 now_ms, oldest_ms;
const gint64 MAX_AGE_MS = 20000;
guint i;
gboolean success = TRUE;
if (!temporary_not_available || !temporary_not_available->len) {
/* nothing outstanding. Clear tracking the routes. */
nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
nm_clear_g_source(&priv->rt6_temporary_not_available_id);
return success;
}
if (priv->rt6_temporary_not_available) {
g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data))
data->dirty = TRUE;
} else {
priv->rt6_temporary_not_available =
g_hash_table_new_full((GHashFunc) nmp_object_id_hash,
(GEqualFunc) nmp_object_id_equal,
(GDestroyNotify) nmp_object_unref,
nm_g_slice_free_fcn(IP6RoutesTemporaryNotAvailableData));
}
now_ms = nm_utils_get_monotonic_timestamp_msec();
oldest_ms = now_ms;
for (i = 0; i < temporary_not_available->len; i++) {
const NMPObject *o = temporary_not_available->pdata[i];
data = g_hash_table_lookup(priv->rt6_temporary_not_available, o);
if (data) {
if (!data->dirty)
continue;
data->dirty = FALSE;
nm_assert(data->timestamp_ms > 0 && data->timestamp_ms <= now_ms);
if (now_ms > data->timestamp_ms + MAX_AGE_MS) {
/* timeout. Could not add this address. */
_LOGW(LOGD_DEVICE,
"failure to add IPv6 route: %s",
nmp_object_to_string(o, NMP_OBJECT_TO_STRING_PUBLIC, NULL, 0));
success = FALSE;
} else
oldest_ms = MIN(data->timestamp_ms, oldest_ms);
continue;
}
data = g_slice_new0(IP6RoutesTemporaryNotAvailableData);
data->timestamp_ms = now_ms;
g_hash_table_insert(priv->rt6_temporary_not_available, (gpointer) nmp_object_ref(o), data);
}
g_hash_table_iter_init(&iter, priv->rt6_temporary_not_available);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &data)) {
if (data->dirty)
g_hash_table_iter_remove(&iter);
}
nm_clear_g_source(&priv->rt6_temporary_not_available_id);
priv->rt6_temporary_not_available_id =
g_timeout_add(oldest_ms + MAX_AGE_MS - now_ms, _rt6_temporary_not_available_timeout, self);
return success;
}
/*****************************************************************************/
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_source_id_4 != 0;
}
NMProxyConfig *
nm_device_get_proxy_config(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->proxy_config;
}
static void
nm_device_set_proxy_config(NMDevice *self, const char *pac_url)
{
NMDevicePrivate *priv;
NMConnection * connection;
NMSettingProxy * s_proxy = NULL;
g_return_if_fail(NM_IS_DEVICE(self));
priv = NM_DEVICE_GET_PRIVATE(self);
g_clear_object(&priv->proxy_config);
priv->proxy_config = nm_proxy_config_new();
if (pac_url) {
nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_AUTO);
nm_proxy_config_set_pac_url(priv->proxy_config, pac_url);
_LOGD(LOGD_PROXY, "proxy: PAC url \"%s\"", pac_url);
} else
nm_proxy_config_set_method(priv->proxy_config, NM_PROXY_CONFIG_METHOD_NONE);
connection = nm_device_get_applied_connection(self);
if (connection)
s_proxy = nm_connection_get_setting_proxy(connection);
if (s_proxy)
nm_proxy_config_merge_setting(priv->proxy_config, s_proxy);
}
/* IP Configuration stuff */
NMDhcpConfig *
nm_device_get_dhcp_config(NMDevice *self, int addr_family)
{
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
nm_assert_addr_family(addr_family);
return NM_DEVICE_GET_PRIVATE(self)->dhcp_data_x[IS_IPv4].config;
}
NMIP4Config *
nm_device_get_ip4_config(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->ip_config_4;
}
static gboolean
nm_device_set_ip_config(NMDevice * self,
int addr_family,
NMIPConfig *new_config,
gboolean commit,
GPtrArray * ip4_dev_route_blacklist)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
const int IS_IPv4 = NM_IS_IPv4(addr_family);
NMIPConfig * old_config;
gboolean has_changes = FALSE;
gboolean success = TRUE;
NMSettingsConnection *settings_connection;
nm_assert_addr_family(addr_family);
nm_assert(!new_config || nm_ip_config_get_addr_family(new_config) == addr_family);
nm_assert(!new_config
|| (new_config && ({
int ip_ifindex = nm_device_get_ip_ifindex(self);
(ip_ifindex > 0 && ip_ifindex == nm_ip_config_get_ifindex(new_config));
})));
nm_assert(IS_IPv4 || !ip4_dev_route_blacklist);
_LOGD(LOGD_IPX(IS_IPv4),
"ip%c-config: update (commit=%d, new-config=%p)",
nm_utils_addr_family_to_char(addr_family),
commit,
new_config);
/* Always commit to nm-platform to update lifetimes */
if (commit && new_config) {
_commit_mtu(self, IS_IPv4 ? NM_IP4_CONFIG(new_config) : priv->ip_config_4);
if (IS_IPv4) {
success = nm_ip4_config_commit(NM_IP4_CONFIG(new_config),
nm_device_get_platform(self),
_get_route_table_sync_mode_stateful(self, AF_INET));
nm_platform_ip4_dev_route_blacklist_set(nm_device_get_platform(self),
nm_ip_config_get_ifindex(new_config),
ip4_dev_route_blacklist);
} else {
gs_unref_ptrarray GPtrArray *temporary_not_available = NULL;
success = nm_ip6_config_commit(NM_IP6_CONFIG(new_config),
nm_device_get_platform(self),
_get_route_table_sync_mode_stateful(self, AF_INET6),
&temporary_not_available);
if (!_rt6_temporary_not_available_set(self, temporary_not_available))
success = FALSE;
}
}
old_config = priv->ip_config_x[IS_IPv4];
if (new_config && old_config) {
/* has_changes is set only on relevant changes, because when the configuration changes,
* this causes a re-read and reset. This should only happen for relevant changes */
nm_ip_config_replace(old_config, new_config, &has_changes);
if (has_changes) {
_LOGD(LOGD_IPX(IS_IPv4),
"ip%c-config: update IP Config instance (%s)",
nm_utils_addr_family_to_char(addr_family),
nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
}
} else if (new_config /*&& !old_config*/) {
has_changes = TRUE;
priv->ip_config_x[IS_IPv4] = g_object_ref(new_config);
if (!nm_dbus_object_is_exported(NM_DBUS_OBJECT(new_config)))
nm_dbus_object_export(NM_DBUS_OBJECT(new_config));
_LOGD(LOGD_IPX(IS_IPv4),
"ip%c-config: set IP Config instance (%s)",
nm_utils_addr_family_to_char(addr_family),
nm_dbus_object_get_path(NM_DBUS_OBJECT(new_config)));
} else if (old_config /*&& !new_config*/) {
has_changes = TRUE;
priv->ip_config_x[IS_IPv4] = NULL;
_LOGD(LOGD_IPX(IS_IPv4),
"ip%c-config: clear IP Config instance (%s)",
nm_utils_addr_family_to_char(addr_family),
nm_dbus_object_get_path(NM_DBUS_OBJECT(old_config)));
if (IS_IPv4) {
/* Device config is invalid if combined config is invalid */
applied_config_clear(&priv->dev_ip_config_4);
} else
priv->needs_ip6_subnet = FALSE;
}
if (has_changes) {
if (old_config != priv->ip_config_x[IS_IPv4])
_notify(self, IS_IPv4 ? PROP_IP4_CONFIG : PROP_IP6_CONFIG);
g_signal_emit(self,
signals[IS_IPv4 ? IP4_CONFIG_CHANGED : IP6_CONFIG_CHANGED],
0,
priv->ip_config_x[IS_IPv4],
old_config);
if (old_config != priv->ip_config_x[IS_IPv4])
nm_dbus_object_clear_and_unexport(&old_config);
if (nm_device_sys_iface_state_is_external(self)
&& (settings_connection = nm_device_get_settings_connection(self))
&& NM_FLAGS_HAS(nm_settings_connection_get_flags(settings_connection),
NM_SETTINGS_CONNECTION_INT_FLAGS_EXTERNAL)
&& nm_active_connection_get_activation_type(NM_ACTIVE_CONNECTION(priv->act_request.obj))
== NM_ACTIVATION_TYPE_EXTERNAL) {
gs_unref_object NMConnection *new_connection = NULL;
new_connection = nm_simple_connection_new_clone(
nm_settings_connection_get_connection(settings_connection));
nm_connection_add_setting(
new_connection,
IS_IPv4 ? nm_ip4_config_create_setting(priv->ip_config_4)
: nm_ip6_config_create_setting(priv->ip_config_6,
_get_maybe_ipv6_disabled(self)));
nm_settings_connection_update(settings_connection,
new_connection,
NM_SETTINGS_CONNECTION_PERSIST_MODE_IN_MEMORY,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_INT_FLAGS_NONE,
NM_SETTINGS_CONNECTION_UPDATE_REASON_NONE,
"update-external",
NULL);
}
nm_device_queue_recheck_assume(self);
if (!IS_IPv4) {
if (priv->ndisc)
ndisc_set_router_config(priv->ndisc, self);
}
}
nm_assert(!old_config || old_config == priv->ip_config_x[IS_IPv4]);
return success;
}
static gboolean
_replace_vpn_config_in_list(GSList **plist, GObject *old, GObject *new)
{
GSList *old_link;
/* Below, assert that @new is not yet tracked, but still behave
* correctly in any case. Don't complain for missing @old since
* it could have been removed when the parent device became
* unmanaged. */
if (old && (old_link = g_slist_find(*plist, old))) {
if (old != new) {
if (new)
old_link->data = g_object_ref(new);
else
*plist = g_slist_delete_link(*plist, old_link);
g_object_unref(old);
}
return TRUE;
}
if (new) {
if (!g_slist_find(*plist, new))
*plist = g_slist_append(*plist, g_object_ref(new));
else
g_return_val_if_reached(TRUE);
return TRUE;
}
return FALSE;
}
void
nm_device_replace_vpn4_config(NMDevice *self, NMIP4Config *old, NMIP4Config *config)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(!old || NM_IS_IP4_CONFIG(old));
nm_assert(!config || NM_IS_IP4_CONFIG(config));
nm_assert(!old || nm_ip4_config_get_ifindex(old) == nm_device_get_ip_ifindex(self));
nm_assert(!config || nm_ip4_config_get_ifindex(config) == nm_device_get_ip_ifindex(self));
if (!_replace_vpn_config_in_list(&priv->vpn_configs_4, (GObject *) old, (GObject *) config))
return;
/* NULL to use existing configs */
if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
_LOGW(LOGD_IP4, "failed to set VPN routes for device");
}
void
nm_device_set_dev2_ip_config(NMDevice *self, int addr_family, NMIPConfig *config)
{
NMDevicePrivate *priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(NM_IN_SET(addr_family, AF_INET, AF_INET6));
g_return_if_fail(!config || nm_ip_config_get_addr_family(config) == addr_family);
priv = NM_DEVICE_GET_PRIVATE(self);
applied_config_init(&priv->dev2_ip_config_x[IS_IPv4], config);
if (!ip_config_merge_and_apply(self, addr_family, TRUE)) {
_LOGW(LOGD_IP,
"failed to set extra device IPv%c configuration",
nm_utils_addr_family_to_char(addr_family));
}
}
void
nm_device_replace_vpn6_config(NMDevice *self, NMIP6Config *old, NMIP6Config *config)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert(!old || NM_IS_IP6_CONFIG(old));
nm_assert(!config || NM_IS_IP6_CONFIG(config));
nm_assert(!old || nm_ip6_config_get_ifindex(old) == nm_device_get_ip_ifindex(self));
nm_assert(!config || nm_ip6_config_get_ifindex(config) == nm_device_get_ip_ifindex(self));
if (!_replace_vpn_config_in_list(&priv->vpn_configs_6, (GObject *) old, (GObject *) config))
return;
/* NULL to use existing configs */
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "failed to set VPN routes for device");
}
NMIP6Config *
nm_device_get_ip6_config(NMDevice *self)
{
g_return_val_if_fail(NM_IS_DEVICE(self), NULL);
return NM_DEVICE_GET_PRIVATE(self)->ip_config_6;
}
/*****************************************************************************/
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_state_4 == NM_DEVICE_IP_STATE_DONE
|| priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE);
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;
if (priv->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
gw = nm_ip4_config_best_default_route_get(priv->ip_config_4);
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_config_6 && priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
gw = nm_ip6_config_best_default_route_get(priv->ip_config_6);
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)
{
gs_free char *value = NULL;
value = nm_config_data_get_device_config(NM_CONFIG_GET_DATA,
NM_CONFIG_KEYFILE_KEY_DEVICE_CARRIER_WAIT_TIMEOUT,
self,
NULL);
return _nm_utils_ascii_str_to_int64(value, 10, 0, G_MAXINT32, 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;
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 {
if (!nm_platform_link_set_up(nm_device_get_platform(self), ifindex, no_firmware))
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);
/* when the link comes up, we must restore IP configuration if necessary. */
if (priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE) {
if (!ip_config_merge_and_apply(self, AF_INET, TRUE))
_LOGW(LOGD_IP4, "failed applying IP4 config after bringing link up");
}
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_DONE) {
if (!ip_config_merge_and_apply(self, AF_INET6, TRUE))
_LOGW(LOGD_IP6, "failed applying IP6 config after bringing link up");
}
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_set_down(nm_device_get_platform(self), ifindex))
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;
}
static void
intersect_ext_config(NMDevice * self,
AppliedConfig *config,
gboolean intersect_addresses,
gboolean intersect_routes)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
NMIPConfig * ext;
guint32 penalty;
int family;
if (!config->orig)
return;
family = nm_ip_config_get_addr_family(config->orig);
penalty = default_route_metric_penalty_get(self, family);
ext = family == AF_INET ? (NMIPConfig *) priv->ext_ip_config_4
: (NMIPConfig *) priv->ext_ip_config_6;
if (config->current) {
nm_ip_config_intersect(config->current,
ext,
intersect_addresses,
intersect_routes,
penalty);
} else {
config->current = nm_ip_config_intersect_alloc(config->orig,
ext,
intersect_addresses,
intersect_routes,
penalty);
}
}
static gboolean
update_ext_ip_config(NMDevice *self, int addr_family, gboolean intersect_configs)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
int ifindex;
GSList * iter;
gboolean is_up;
nm_assert_addr_family(addr_family);
ifindex = nm_device_get_ip_ifindex(self);
if (!ifindex)
return FALSE;
is_up = nm_platform_link_is_up(nm_device_get_platform(self), ifindex);
if (NM_IS_IPv4(addr_family)) {
g_clear_object(&priv->ext_ip_config_4);
priv->ext_ip_config_4 = nm_ip4_config_capture(nm_device_get_multi_index(self),
nm_device_get_platform(self),
ifindex);
if (priv->ext_ip_config_4) {
if (intersect_configs) {
/* This function was called upon external changes. Remove the configuration
* (addresses,routes) that is no longer present externally from the internal
* config. This way, we don't re-add addresses that were manually removed
* by the user. */
if (priv->con_ip_config_4) {
nm_ip4_config_intersect(priv->con_ip_config_4,
priv->ext_ip_config_4,
TRUE,
is_up,
default_route_metric_penalty_get(self, AF_INET));
}
intersect_ext_config(self, &priv->dev_ip_config_4, TRUE, is_up);
intersect_ext_config(self, &priv->dev2_ip_config_4, TRUE, is_up);
for (iter = priv->vpn_configs_4; iter; iter = iter->next)
nm_ip4_config_intersect(iter->data, priv->ext_ip_config_4, TRUE, is_up, 0);
}
/* Remove parts from ext_ip_config_4 to only contain the information that
* was configured externally -- we already have the same configuration from
* internal origins. */
if (priv->con_ip_config_4) {
nm_ip4_config_subtract(priv->ext_ip_config_4,
priv->con_ip_config_4,
default_route_metric_penalty_get(self, AF_INET));
}
if (applied_config_get_current(&priv->dev_ip_config_4)) {
nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4,
applied_config_get_current(&priv->dev_ip_config_4),
default_route_metric_penalty_get(self, AF_INET));
}
if (applied_config_get_current(&priv->dev2_ip_config_4)) {
nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_4,
applied_config_get_current(&priv->dev2_ip_config_4),
default_route_metric_penalty_get(self, AF_INET));
}
for (iter = priv->vpn_configs_4; iter; iter = iter->next)
nm_ip4_config_subtract(priv->ext_ip_config_4, iter->data, 0);
}
} else {
nm_assert(!NM_IS_IPv4(addr_family));
g_clear_object(&priv->ext_ip_config_6);
g_clear_object(&priv->ext_ip6_config_captured);
priv->ext_ip6_config_captured =
nm_ip6_config_capture(nm_device_get_multi_index(self),
nm_device_get_platform(self),
ifindex,
NM_SETTING_IP6_CONFIG_PRIVACY_UNKNOWN);
if (priv->ext_ip6_config_captured) {
priv->ext_ip_config_6 = nm_ip6_config_new_cloned(priv->ext_ip6_config_captured);
if (intersect_configs) {
/* This function was called upon external changes. Remove the configuration
* (addresses,routes) that is no longer present externally from the internal
* config. This way, we don't re-add addresses that were manually removed
* by the user. */
if (priv->con_ip_config_6) {
nm_ip6_config_intersect(priv->con_ip_config_6,
priv->ext_ip_config_6,
is_up,
is_up,
default_route_metric_penalty_get(self, AF_INET6));
}
intersect_ext_config(self, &priv->ac_ip6_config, is_up, is_up);
intersect_ext_config(self, &priv->dhcp6.ip6_config, is_up, is_up);
intersect_ext_config(self, &priv->dev2_ip_config_6, is_up, is_up);
for (iter = priv->vpn_configs_6; iter; iter = iter->next)
nm_ip6_config_intersect(iter->data, priv->ext_ip_config_6, is_up, is_up, 0);
if (is_up && priv->ipv6ll_has
&& !nm_ip6_config_lookup_address(priv->ext_ip_config_6, &priv->ipv6ll_addr))
priv->ipv6ll_has = FALSE;
}
/* Remove parts from ext_ip_config_6 to only contain the information that
* was configured externally -- we already have the same configuration from
* internal origins. */
if (priv->con_ip_config_6) {
nm_ip6_config_subtract(priv->ext_ip_config_6,
priv->con_ip_config_6,
default_route_metric_penalty_get(self, AF_INET6));
}
if (applied_config_get_current(&priv->ac_ip6_config)) {
nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
applied_config_get_current(&priv->ac_ip6_config),
default_route_metric_penalty_get(self, AF_INET6));
}
if (applied_config_get_current(&priv->dhcp6.ip6_config)) {
nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
applied_config_get_current(&priv->dhcp6.ip6_config),
default_route_metric_penalty_get(self, AF_INET6));
}
if (applied_config_get_current(&priv->dev2_ip_config_6)) {
nm_ip_config_subtract((NMIPConfig *) priv->ext_ip_config_6,
applied_config_get_current(&priv->dev2_ip_config_6),
default_route_metric_penalty_get(self, AF_INET6));
}
for (iter = priv->vpn_configs_6; iter; iter = iter->next)
nm_ip6_config_subtract(priv->ext_ip_config_6, iter->data, 0);
}
}
return TRUE;
}
static void
update_ip_config(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
nm_assert_addr_family(addr_family);
if (NM_IS_IPv4(addr_family))
priv->update_ip_config_completed_v4 = TRUE;
else
priv->update_ip_config_completed_v6 = TRUE;
if (update_ext_ip_config(self, addr_family, TRUE)) {
if (NM_IS_IPv4(addr_family)) {
if (priv->ext_ip_config_4)
ip_config_merge_and_apply(self, AF_INET, FALSE);
} else {
if (priv->ext_ip6_config_captured)
ip_config_merge_and_apply(self, AF_INET6, FALSE);
}
}
}
void
nm_device_capture_initial_config(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (!priv->update_ip_config_completed_v4)
update_ip_config(self, AF_INET);
if (!priv->update_ip_config_completed_v6)
update_ip_config(self, AF_INET6);
}
static gboolean
queued_ip_config_change(NMDevice *self, int addr_family)
{
NMDevicePrivate *priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
g_return_val_if_fail(NM_IS_DEVICE(self), G_SOURCE_REMOVE);
priv = NM_DEVICE_GET_PRIVATE(self);
/* Wait for any queued state changes */
if (priv->queued_state.id)
return G_SOURCE_CONTINUE;
/* If a commit is scheduled, this function would potentially interfere with
* it changing IP configurations before they are applied. Postpone the
* update in such case.
*/
if (priv->activation_source_id_x[IS_IPv4] != 0
&& priv->activation_source_func_x[IS_IPv4]
== activate_stage5_ip_config_result_x_fcn(addr_family))
return G_SOURCE_CONTINUE;
priv->queued_ip_config_id_x[IS_IPv4] = 0;
update_ip_config(self, addr_family);
if (!IS_IPv4) {
/* Check whether we need to complete waiting for link-local.
* We are also called from an idle handler, so no problem doing state transitions
* now. */
linklocal6_check_complete(self);
}
if (!IS_IPv4) {
NMPlatform * platform;
GSList * dad6_failed_addrs, *iter;
const NMPlatformLink *pllink;
dad6_failed_addrs = g_steal_pointer(&priv->dad6_failed_addrs);
if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING
&& priv->ifindex > 0 && !nm_device_sys_iface_state_is_external(self)
&& (platform = nm_device_get_platform(self))
&& (pllink = nm_platform_link_get(platform, priv->ifindex))
&& (pllink->n_ifi_flags & IFF_UP)) {
gboolean need_ipv6ll = FALSE;
NMNDiscConfigMap ndisc_config_changed = NM_NDISC_CONFIG_NONE;
/* Handle DAD failures */
for (iter = dad6_failed_addrs; iter; iter = iter->next) {
const NMPObject * obj = iter->data;
const NMPlatformIP6Address *addr;
if (!nm_ndisc_dad_addr_is_fail_candidate(platform, obj))
continue;
addr = NMP_OBJECT_CAST_IP6_ADDRESS(obj);
_LOGI(LOGD_IP6,
"ipv6: duplicate address check failed for the %s address",
nm_platform_ip6_address_to_string(addr, NULL, 0));
if (IN6_IS_ADDR_LINKLOCAL(&addr->address))
need_ipv6ll = TRUE;
else if (priv->ndisc)
ndisc_config_changed |= nm_ndisc_dad_failed(priv->ndisc, &addr->address, FALSE);
}
if (ndisc_config_changed != NM_NDISC_CONFIG_NONE)
nm_ndisc_emit_config_change(priv->ndisc, ndisc_config_changed);
/* If no IPv6 link-local address exists but other addresses do then we
* must add the LL address to remain conformant with RFC 3513 chapter 2.1
* ("Addressing Model"): "All interfaces are required to have at least
* one link-local unicast address".
*/
if (priv->ip_config_6 && nm_ip6_config_get_num_addresses(priv->ip_config_6))
need_ipv6ll = TRUE;
if (need_ipv6ll)
check_and_add_ipv6ll_addr(self);
}
g_slist_free_full(dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
}
if (!IS_IPv4) {
/* Check if DAD is still pending */
if (priv->ip_state_6 == NM_DEVICE_IP_STATE_CONF && priv->dad6_ip6_config
&& priv->ext_ip6_config_captured
&& !nm_ip6_config_has_any_dad_pending(priv->ext_ip6_config_captured,
priv->dad6_ip6_config)) {
_LOGD(LOGD_DEVICE | LOGD_IP6, "IPv6 DAD terminated");
g_clear_object(&priv->dad6_ip6_config);
_set_ip_state(self, addr_family, NM_DEVICE_IP_STATE_DONE);
check_ip_state(self, FALSE, TRUE);
if (priv->rt6_temporary_not_available)
nm_device_activate_schedule_ip_config_result(self, AF_INET6, NULL);
}
}
set_unmanaged_external_down(self, TRUE);
return G_SOURCE_REMOVE;
}
static gboolean
queued_ip4_config_change(gpointer user_data)
{
return queued_ip_config_change(user_data, AF_INET);
}
static gboolean
queued_ip6_config_change(gpointer user_data)
{
return queued_ip_config_change(user_data, AF_INET6);
}
static void
device_ipx_changed(NMPlatform * platform,
int obj_type_i,
int ifindex,
gconstpointer platform_object,
int change_type_i,
NMDevice * self)
{
const NMPObjectType obj_type = obj_type_i;
const NMPlatformSignalChangeType change_type = change_type_i;
NMDevicePrivate * priv;
const NMPlatformIP6Address * addr;
if (nm_device_get_ip_ifindex(self) != ifindex)
return;
if (!nm_device_is_real(self))
return;
if (nm_device_get_unmanaged_flags(self, NM_UNMANAGED_PLATFORM_INIT)) {
/* ignore all platform signals until the link is initialized in platform. */
return;
}
priv = NM_DEVICE_GET_PRIVATE(self);
switch (obj_type) {
case NMP_OBJECT_TYPE_IP4_ADDRESS:
case NMP_OBJECT_TYPE_IP4_ROUTE:
if (!priv->queued_ip_config_id_4) {
priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self);
_LOGD(LOGD_DEVICE, "queued IP4 config change");
}
break;
case NMP_OBJECT_TYPE_IP6_ADDRESS:
addr = platform_object;
if (priv->state > NM_DEVICE_STATE_DISCONNECTED && priv->state < NM_DEVICE_STATE_DEACTIVATING
&& nm_ndisc_dad_addr_is_fail_candidate_event(change_type, addr)) {
priv->dad6_failed_addrs =
g_slist_prepend(priv->dad6_failed_addrs,
(gpointer) nmp_object_ref(NMP_OBJECT_UP_CAST(addr)));
}
/* fall-through */
case NMP_OBJECT_TYPE_IP6_ROUTE:
if (!priv->queued_ip_config_id_6) {
priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self);
_LOGD(LOGD_DEVICE, "queued IP6 config change");
}
break;
default:
g_return_if_reached();
}
}
/*****************************************************************************/
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_utils_strbuf_append_c(&b, &len, ',');
add_separator = TRUE;
tmp2 = strchr(tmp, ',');
if (tmp2)
tmp2[0] = '\0';
nm_utils_strbuf_append_c(&b, &len, '!');
nm_utils_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);
}
/* trigger an initial update of IP configuration. */
nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_4));
nm_assert_se(!nm_clear_g_source(&priv->queued_ip_config_id_6));
priv->queued_ip_config_id_4 = g_idle_add(queued_ip4_config_change, self);
priv->queued_ip_config_id_6 = g_idle_add(queued_ip6_config_change, self);
if (!priv->pending_actions) {
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 ",
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 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));
nm_device_set_unmanaged_by_flags(self,
NM_UNMANAGED_USER_SETTINGS,
!!unmanaged,
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_set_dhcp_anycast_address(NMDevice *self, const char *addr)
{
NMDevicePrivate *priv;
g_return_if_fail(NM_IS_DEVICE(self));
g_return_if_fail(!addr || nm_utils_hwaddr_valid(addr, ETH_ALEN));
priv = NM_DEVICE_GET_PRIVATE(self);
g_free(priv->dhcp_anycast_address);
priv->dhcp_anycast_address = g_strdup(addr);
}
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->ip_config_4 && priv->ip_state_4 == NM_DEVICE_IP_STATE_DONE
&& nm_ip4_config_get_metered(priv->ip_config_4))
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);
GSList * iter;
guint count = 0;
g_return_val_if_fail(action, FALSE);
/* Check if the action is already pending. Cannot add duplicate actions */
for (iter = priv->pending_actions; iter; iter = iter->next) {
if (nm_streq(action, iter->data)) {
if (assert_not_yet_pending) {
_LOGW(LOGD_DEVICE,
"add_pending_action (%d): '%s' already pending",
count + g_slist_length(iter),
action);
g_return_val_if_reached(FALSE);
} else {
_LOGT(LOGD_DEVICE,
"add_pending_action (%d): '%s' already pending (expected)",
count + g_slist_length(iter),
action);
}
return FALSE;
}
count++;
}
priv->pending_actions = g_slist_prepend(priv->pending_actions, (char *) action);
count++;
_LOGD(LOGD_DEVICE, "add_pending_action (%d): '%s'", count, action);
if (count == 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;
GSList * iter, *next;
guint count = 0;
g_return_val_if_fail(self, FALSE);
g_return_val_if_fail(action, FALSE);
priv = NM_DEVICE_GET_PRIVATE(self);
for (iter = priv->pending_actions; iter; iter = next) {
next = iter->next;
if (nm_streq(action, iter->data)) {
_LOGD(LOGD_DEVICE,
"remove_pending_action (%d): '%s'",
count + g_slist_length(iter->next), /* length excluding 'iter' */
action);
priv->pending_actions = g_slist_delete_link(priv->pending_actions, iter);
if (priv->pending_actions == NULL)
_notify(self, PROP_HAS_PENDING_ACTION);
return TRUE;
}
count++;
}
if (assert_is_pending) {
_LOGW(LOGD_DEVICE, "remove_pending_action (%d): '%s' not pending", count, action);
g_return_val_if_reached(FALSE);
} else
_LOGT(LOGD_DEVICE,
"remove_pending_action (%d): '%s' not pending (expected)",
count,
action);
return FALSE;
}
const char *
nm_device_has_pending_action_reason(NMDevice *self)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
if (priv->pending_actions) {
if (!priv->pending_actions->next && nm_device_get_state(self) == NM_DEVICE_STATE_ACTIVATED
&& nm_streq(priv->pending_actions->data, 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->data;
}
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_firewall_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, AF_INET);
activation_source_clear(self, AF_INET6);
}
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_firewall_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);
nm_clear_pointer(&priv->shared_ip_handle, nm_netns_shared_ip_release);
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);
_cleanup_ip_pre(self, AF_INET6, cleanup_type);
}
static void
_cleanup_generic_post(NMDevice *self, CleanupType cleanup_type)
{
NMDevicePrivate *priv = NM_DEVICE_GET_PRIVATE(self);
priv->v4_commit_first_time = TRUE;
priv->v6_commit_first_time = TRUE;
priv->v4_route_table_initialized = FALSE;
priv->v6_route_table_initialized = FALSE;
priv->v4_route_table_all_sync_before = FALSE;
priv->v6_route_table_all_sync_before = FALSE;
priv->default_route_metric_penalty_ip4_has = FALSE;
priv->default_route_metric_penalty_ip6_has = FALSE;
priv->linklocal6_dad_counter = 0;
priv->mtu_force_set_done = FALSE;
/* Clean up IP configs; this does not actually deconfigure the
* interface; the caller must flush routes and addresses explicitly.
*/
nm_device_set_ip_config(self, AF_INET, NULL, TRUE, NULL);
nm_device_set_ip_config(self, AF_INET6, NULL, TRUE, NULL);
g_clear_object(&priv->proxy_config);
g_clear_object(&priv->con_ip_config_4);
applied_config_clear(&priv->dev_ip_config_4);
applied_config_clear(&priv->dev2_ip_config_4);
g_clear_object(&priv->ext_ip_config_4);
g_clear_object(&priv->ip_config_4);
g_clear_object(&priv->con_ip_config_6);
applied_config_clear(&priv->ac_ip6_config);
g_clear_object(&priv->ext_ip_config_6);
g_clear_object(&priv->ext_ip6_config_captured);
applied_config_clear(&priv->dev2_ip_config_6);
g_clear_object(&priv->ip_config_6);
g_clear_object(&priv->dad6_ip6_config);
priv->ipv6ll_has = FALSE;
memset(&priv->ipv6ll_addr, 0, sizeof(priv->ipv6ll_addr));
nm_clear_pointer(&priv->rt6_temporary_not_available, g_hash_table_unref);
nm_clear_g_source(&priv->rt6_temporary_not_available_id);
g_slist_free_full(priv->vpn_configs_4, g_object_unref);
priv->vpn_configs_4 = NULL;
g_slist_free_full(priv->vpn_configs_6, g_object_unref);
priv->vpn_configs_6 = NULL;
/* We no longer accept the delegations. nm_device_set_ip_config(NULL)
* above disables them. */
nm_assert(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, nm_device_get_ip_ifindex(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_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]",
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) {
set_disable_ipv6(self, "1");
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);
NMUtilsIPv6IfaceId iid = {};
nm_platform_ip_route_flush(platform, AF_UNSPEC, ifindex);
nm_platform_ip_address_flush(platform, AF_UNSPEC, ifindex);
nm_platform_tfilter_sync(platform, ifindex, NULL);
nm_platform_qdisc_sync(platform, ifindex, NULL);
set_ipv6_token(self, iid, "::");
}
}
_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);
if (priv->lldp_listener)
nm_lldp_listener_stop(priv->lldp_listener);
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);
_cleanup_generic_post(self, cleanup_type);
}
static void
deactivate_reset_hw_addr(NMDevice *self)
{
nm_device_hw_addr_reset(self, "deactivate");
}
static char *
find_dhcp4_address(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
const NMPlatformIP4Address *a;
NMDedupMultiIter ipconf_iter;
if (!priv->ip_config_4)
return NULL;
nm_ip_config_iter_ip4_address_for_each (&ipconf_iter, priv->ip_config_4, &a) {
if (a->addr_source == NM_IP_CONFIG_SOURCE_DHCP)
return nm_utils_inet4_ntop_dup(a->address);
}
return NULL;
}
void
nm_device_spawn_iface_helper(NMDevice *self)
{
NMDevicePrivate * priv = NM_DEVICE_GET_PRIVATE(self);
gboolean configured = FALSE;
NMConnection * connection;
GError * error = NULL;
const char * method;
GPtrArray * argv;
gs_free char * dhcp4_address = NULL;
char * logging_backend;
NMUtilsStableType stable_type;
const char * stable_id;
if (priv->state != NM_DEVICE_STATE_ACTIVATED)
return;
if (!nm_device_can_assume_connections(self))
return;
connection = nm_device_get_applied_connection(self);
g_return_if_fail(connection);
argv = g_ptr_array_sized_new(10);
g_ptr_array_set_free_func(argv, g_free);
g_ptr_array_add(argv, g_strdup(LIBEXECDIR "/nm-iface-helper"));
g_ptr_array_add(argv, g_strdup("--ifname"));
g_ptr_array_add(argv, g_strdup(nm_device_get_ip_iface(self)));
g_ptr_array_add(argv, g_strdup("--uuid"));
g_ptr_array_add(argv, g_strdup(nm_connection_get_uuid(connection)));
stable_id = _prop_get_connection_stable_id(self, connection, &stable_type);
if (stable_type != NM_UTILS_STABLE_TYPE_UUID) {
g_ptr_array_add(argv, g_strdup("--stable-id"));
g_ptr_array_add(argv, g_strdup_printf("%d %s", (int) stable_type, stable_id));
}
logging_backend =
nm_config_data_get_value(NM_CONFIG_GET_DATA_ORIG,
NM_CONFIG_KEYFILE_GROUP_LOGGING,
NM_CONFIG_KEYFILE_KEY_LOGGING_BACKEND,
NM_CONFIG_GET_VALUE_STRIP | NM_CONFIG_GET_VALUE_NO_EMPTY);
if (logging_backend) {
g_ptr_array_add(argv, g_strdup("--logging-backend"));
g_ptr_array_add(argv, logging_backend);
}
g_ptr_array_add(argv, g_strdup("--log-level"));
g_ptr_array_add(argv, g_strdup(nm_logging_level_to_string()));
g_ptr_array_add(argv, g_strdup("--log-domains"));
g_ptr_array_add(argv, g_strdup(nm_logging_domains_to_string()));
dhcp4_address = find_dhcp4_address(self);
method = nm_device_get_effective_ip_config_method(self, AF_INET);
if (nm_streq(method, NM_SETTING_IP4_CONFIG_METHOD_AUTO)) {
NMSettingIPConfig *s_ip4;
s_ip4 = nm_connection_get_setting_ip4_config(connection);
nm_assert(s_ip4);
g_ptr_array_add(argv, g_strdup("--priority4"));
g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET)));
g_ptr_array_add(argv, g_strdup("--dhcp4"));
g_ptr_array_add(argv, g_strdup(dhcp4_address));
if (nm_setting_ip_config_get_may_fail(s_ip4) == FALSE)
g_ptr_array_add(argv, g_strdup("--dhcp4-required"));
if (priv->dhcp_data_4.client) {
const char *hostname;
GBytes * client_id;
client_id = nm_dhcp_client_get_client_id(priv->dhcp_data_4.client);
if (client_id) {
g_ptr_array_add(argv, g_strdup("--dhcp4-clientid"));
g_ptr_array_add(argv,
nm_utils_bin2hexstr_full(g_bytes_get_data(client_id, NULL),
g_bytes_get_size(client_id),
':',
FALSE,
NULL));
}
hostname = nm_dhcp_client_get_hostname(priv->dhcp_data_4.client);
if (hostname) {
if (nm_dhcp_client_get_use_fqdn(priv->dhcp_data_4.client))
g_ptr_array_add(argv, g_strdup("--dhcp4-fqdn"));
else
g_ptr_array_add(argv, g_strdup("--dhcp4-hostname"));
g_ptr_array_add(argv, g_strdup(hostname));
}
}
configured = TRUE;
}
method = nm_utils_get_ip_config_method(connection, AF_INET6);
if (nm_streq(method, NM_SETTING_IP6_CONFIG_METHOD_AUTO)) {
NMSettingIPConfig *s_ip6;
NMUtilsIPv6IfaceId iid = NM_UTILS_IPV6_IFACE_ID_INIT;
s_ip6 = nm_connection_get_setting_ip6_config(connection);
g_assert(s_ip6);
g_ptr_array_add(argv, g_strdup("--priority6"));
g_ptr_array_add(argv, g_strdup_printf("%u", nm_device_get_route_metric(self, AF_INET6)));
g_ptr_array_add(argv, g_strdup("--slaac"));
if (nm_setting_ip_config_get_may_fail(s_ip6) == FALSE)
g_ptr_array_add(argv, g_strdup("--slaac-required"));
g_ptr_array_add(argv, g_strdup("--slaac-tempaddr"));
g_ptr_array_add(argv, g_strdup_printf("%d", priv->ndisc_use_tempaddr));
if (nm_device_get_ip_iface_identifier(self, &iid, FALSE)) {
g_ptr_array_add(argv, g_strdup("--iid"));
g_ptr_array_add(
argv,
nm_utils_bin2hexstr_full(iid.id_u8, sizeof(NMUtilsIPv6IfaceId), ':', FALSE, NULL));
}
g_ptr_array_add(argv, g_strdup("--addr-gen-mode"));
g_ptr_array_add(
argv,
g_strdup_printf("%d",
nm_setting_ip6_config_get_addr_gen_mode(NM_SETTING_IP6_CONFIG(s_ip6))));
configured = TRUE;
}
if (configured) {
GPid pid;
g_ptr_array_add(argv, NULL);
if (nm_logging_enabled(LOGL_DEBUG, LOGD_DEVICE)) {
char *tmp;
tmp = g_strjoinv(" ", (char **) argv->pdata);
_LOGD(LOGD_DEVICE, "running '%s'", tmp);
g_free(tmp);
}
if (g_spawn_async(NULL,
(char **) argv->pdata,
NULL,
G_SPAWN_DO_NOT_REAP_CHILD,
NULL,
NULL,
&pid,
&error)) {
_LOGI(LOGD_DEVICE, "spawned helper PID %u", (guint) pid);
} else {
_LOGW(LOGD_DEVICE, "failed to spawn helper: %s", error->message);
g_error_free(error);
}
}
g_ptr_array_unref(argv);
}
/*****************************************************************************/
static gboolean
ip_config_valid(NMDeviceState state)
{
return (state == NM_DEVICE_STATE_UNMANAGED)
|| (state >= NM_DEVICE_STATE_IP_CHECK && state <= NM_DEVICE_STATE_DEACTIVATING);
}
static void
notify_ip_properties(NMDevice *self)
{
_notify(self, PROP_IP_IFACE);
_notify(self, PROP_IP4_CONFIG);
_notify(self, PROP_DHCP4_CONFIG);
_notify(self, PROP_IP6_CONFIG);
_notify(self, PROP_DHCP6_CONFIG);
}
static void
ip6_managed_setup(NMDevice *self)
{
set_nm_ipv6ll(self, TRUE);
set_disable_ipv6(self, "1");
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)
{
NMDevicePrivate *priv;
NMDeviceState old_state;
gs_unref_object NMActRequest *req = NULL;
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);
/* Track re-entry */
g_warn_if_fail(priv->in_state_changed == FALSE);
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_str(old_state),
nm_device_state_to_str(state),
reason_to_string_a(reason),
_sys_iface_state_to_str(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_str(old_state),
nm_device_state_to_str(state),
reason_to_string_a(reason),
_sys_iface_state_to_str(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 = TRUE;
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) {
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");
set_nm_ipv6ll(self, FALSE);
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) {
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.
*/
set_nm_ipv6ll(self, TRUE);
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);
_cleanup_ip_pre(self, AF_INET6, CLEANUP_TYPE_DECONFIGURE);
}
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);
if (priv->proxy_config)
_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);
/* IP-related properties are only valid when the device has IP configuration;
* now that it does, ensure their change notifications are emitted.
*/
notify_ip_properties(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);
}
}
/* IP-related properties are only valid when the device has IP configuration.
* If it no longer does, ensure their change notifications are emitted.
*/
if (ip_config_valid(old_state) && !ip_config_valid(state))
notify_ip_properties(self);
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);
priv->in_state_changed = FALSE;
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_str(priv->queued_state.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, 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_str(priv->queued_state.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: ",
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, 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_str(priv->queued_state.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,
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_str(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_str(priv->queued_state.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, 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;
}
/*****************************************************************************/
/* 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];
size_t len = 0;
gboolean success_read;
int ifindex;
const NMPlatformLink * pllink;
const NMConfigDeviceStateData *dev_state;
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), ifindex, buf, &len);
if (success_read && priv->hw_addr_len == len) {
priv->hw_addr_perm_fake = FALSE;
priv->hw_addr_perm = nm_utils_hwaddr_ntoa(buf, 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,
char ** hwaddr_detail,
GError ** error)
{
NMDevicePrivate *priv;
gs_free char * addr_setting_free = NULL;
gs_free char * hw_addr_generated = NULL;
gs_free char * generate_mac_address_mask_tmp = NULL;
const char * addr, *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 = addr_setting =
_prop_get_x_cloned_mac_address(self, connection, is_wifi, &addr_setting_free);
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, g_steal_pointer(&addr_setting_free) ?: g_strdup(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,
g_steal_pointer(&addr_setting_free) ?: g_strdup(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,
&generate_mac_address_mask_tmp));
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,
&generate_mac_address_mask_tmp));
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, g_steal_pointer(&addr_setting_free) ?: g_strdup(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;
gs_free 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 * hostname = NULL;
gs_free char * addr_str = NULL;
gs_free_error GError *error = NULL;
hostname = g_resolver_lookup_by_address_finish(G_RESOLVER(source), 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;
resolver->hostname = g_strdup(hostname);
_LOGD(LOGD_DNS,
"hostname-from-dns: lookup done for %s, result %s%s%s",
(addr_str = g_inet_address_to_string(resolver->address)),
NM_PRINT_FMT_QUOTE_STRING(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);
}
/* return value is valid only immediately */
const char *
nm_device_get_hostname_from_dns_lookup(NMDevice *self, int addr_family, gboolean *out_wait)
{
NMDevicePrivate * priv;
const int IS_IPv4 = NM_IS_IPv4(addr_family);
HostnameResolver *resolver;
NMIPConfig * ip_config;
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){
.resolver = g_resolver_get_default(),
.device = self,
.addr_family = addr_family,
.state = RESOLVER_WAIT_ADDRESS,
};
priv->hostname_resolver_x[IS_IPv4] = resolver;
}
/* Determine the first address of the interface and
* whether it changed from the previous lookup */
ip_config = priv->ip_config_x[IS_IPv4];
if (ip_config) {
const NMPlatformIPAddress *addr;
addr = nm_ip_config_get_first_address(ip_config);
if (addr) {
new_address = g_inet_address_new_from_bytes(addr->address_ptr,
IS_IPv4 ? G_SOCKET_FAMILY_IPV4
: G_SOCKET_FAMILY_IPV6);
}
}
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) {
gs_free char *str = NULL;
_LOGT(LOGD_DNS,
"hostname-from-dns: starting lookup for address %s",
(str = g_inet_address_to_string(new_address)));
resolver->state = RESOLVER_IN_PROGRESS;
resolver->cancellable = g_cancellable_new();
resolver->address = g_steal_pointer(&new_address);
g_resolver_lookup_by_address_async(resolver->resolver,
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_start);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_4);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage4_ip_config_timeout_6);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_4);
FUNC_TO_STRING_CHECK_AND_RETURN(func, activate_stage5_ip_config_result_6);
g_return_val_if_reached("unknown");
}
/*****************************************************************************/
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);
GVariantBuilder array_builder;
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:
if (ip_config_valid(priv->state)) {
g_value_take_string(
value,
nm_utils_str_utf8safe_escape_cp(nm_device_get_ip_iface(self),
NM_UTILS_STR_UTF8_SAFE_FLAG_ESCAPE_CTRL));
} else
g_value_set_string(value, NULL);
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,
ip_config_valid(priv->state) ? priv->ip_config_4
: NULL);
break;
case PROP_DHCP4_CONFIG:
nm_dbus_utils_g_value_set_object_path(
value,
ip_config_valid(priv->state) ? priv->dhcp_data_4.config : NULL);
break;
case PROP_IP6_CONFIG:
nm_dbus_utils_g_value_set_object_path(value,
ip_config_valid(priv->state) ? priv->ip_config_6
: NULL);
break;
case PROP_DHCP6_CONFIG:
nm_dbus_utils_g_value_set_object_path(
value,
ip_config_valid(priv->state) ? priv->dhcp_data_6.config : NULL);
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_RFKILL_TYPE:
g_value_set_uint(value, priv->rfkill_type);
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:
if (priv->lldp_listener)
g_value_set_variant(value, nm_lldp_listener_get_neighbors(priv->lldp_listener));
else {
g_variant_builder_init(&array_builder, G_VARIANT_TYPE("aa{sv}"));
g_value_take_variant(value, g_variant_builder_end(&array_builder));
}
break;
case PROP_REAL:
g_value_set_boolean(value, nm_device_is_real(self));
break;
case PROP_SLAVES:
{
CList *slave_iter;
char **slave_list;
gsize i, n;
n = c_list_length(&priv->slaves);
slave_list = g_new(char *, n + 1);
i = 0;
c_list_for_each (slave_iter, &priv->slaves) {
SlaveInfo * info = c_list_entry(slave_iter, SlaveInfo, 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)
slave_list[i++] = g_strdup(path);
}
nm_assert(i <= n);
slave_list[i] = NULL;
g_value_take_boxed(value, slave_list);
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_RFKILL_TYPE:
/* construct-only */
priv->rfkill_type = g_value_get_uint(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->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->rfkill_type = RFKILL_TYPE_UNKNOWN;
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->v4_commit_first_time = TRUE;
priv->v6_commit_first_time = TRUE;
}
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);
/* Watch for external IP config changes */
platform = nm_device_get_platform(self);
g_signal_connect(platform,
NM_PLATFORM_SIGNAL_IP4_ADDRESS_CHANGED,
G_CALLBACK(device_ipx_changed),
self);
g_signal_connect(platform,
NM_PLATFORM_SIGNAL_IP6_ADDRESS_CHANGED,
G_CALLBACK(device_ipx_changed),
self);
g_signal_connect(platform,
NM_PLATFORM_SIGNAL_IP4_ROUTE_CHANGED,
G_CALLBACK(device_ipx_changed),
self);
g_signal_connect(platform,
NM_PLATFORM_SIGNAL_IP6_ROUTE_CHANGED,
G_CALLBACK(device_ipx_changed),
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(device_ipx_changed), self);
g_signal_handlers_disconnect_by_func(platform, G_CALLBACK(link_changed_cb), self);
arp_cleanup(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 */
set_nm_ipv6ll(self, FALSE);
_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(&priv->stats.timeout_id);
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);
if (priv->lldp_listener) {
g_signal_handlers_disconnect_by_func(priv->lldp_listener,
G_CALLBACK(lldp_neighbors_changed),
self);
nm_lldp_listener_stop(priv->lldp_listener);
g_clear_object(&priv->lldp_listener);
}
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_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_slist_free(priv->pending_actions);
g_slist_free_full(priv->dad6_failed_addrs, (GDestroyNotify) nmp_object_unref);
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->dhcp_anycast_address);
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);
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_L("Udi", "s", NM_DEVICE_UDI),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Path", "s", NM_DEVICE_PATH),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Interface", "s", NM_DEVICE_IFACE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("IpInterface",
"s",
NM_DEVICE_IP_IFACE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Driver", "s", NM_DEVICE_DRIVER),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DriverVersion",
"s",
NM_DEVICE_DRIVER_VERSION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareVersion",
"s",
NM_DEVICE_FIRMWARE_VERSION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Capabilities",
"u",
NM_DEVICE_CAPABILITIES),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Address",
"u",
NM_DEVICE_IP4_ADDRESS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("State", "u", NM_DEVICE_STATE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("StateReason",
"(uu)",
NM_DEVICE_STATE_REASON),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("ActiveConnection",
"o",
NM_DEVICE_ACTIVE_CONNECTION),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip4Config",
"o",
NM_DEVICE_IP4_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp4Config",
"o",
NM_DEVICE_DHCP4_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Ip6Config",
"o",
NM_DEVICE_IP6_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Dhcp6Config",
"o",
NM_DEVICE_DHCP6_CONFIG),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Managed",
"b",
NM_DEVICE_MANAGED,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
NM_AUDIT_OP_DEVICE_MANAGED),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READWRITABLE_L("Autoconnect",
"b",
NM_DEVICE_AUTOCONNECT,
NM_AUTH_PERMISSION_NETWORK_CONTROL,
NM_AUDIT_OP_DEVICE_AUTOCONNECT),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("FirmwareMissing",
"b",
NM_DEVICE_FIRMWARE_MISSING),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("NmPluginMissing",
"b",
NM_DEVICE_NM_PLUGIN_MISSING),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("DeviceType",
"u",
NM_DEVICE_DEVICE_TYPE),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("AvailableConnections",
"ao",
NM_DEVICE_AVAILABLE_CONNECTIONS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("PhysicalPortId",
"s",
NM_DEVICE_PHYSICAL_PORT_ID),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Mtu", "u", NM_DEVICE_MTU),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("Metered", "u", NM_DEVICE_METERED),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("LldpNeighbors",
"aa{sv}",
NM_DEVICE_LLDP_NEIGHBORS),
NM_DEFINE_DBUS_PROPERTY_INFO_EXTENDED_READABLE_L("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), ), ),
};
const NMDBusInterfaceInfoExtended nm_interface_info_device_statistics = {
.parent = NM_DEFINE_GDBUS_INTERFACE_INFO_INIT(
NM_DBUS_INTERFACE_DEVICE_STATISTICS,
.signals = NM_DEFINE_GDBUS_SIGNAL_INFOS(&nm_signal_info_property_changed_legacy, ),
.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, &nm_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->act_stage3_ip_config_start = act_stage3_ip_config_start;
klass->act_stage4_ip_config_timeout = act_stage4_ip_config_timeout;
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;
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_RFKILL_TYPE] =
g_param_spec_uint(NM_DEVICE_RFKILL_TYPE,
"",
"",
RFKILL_TYPE_WLAN,
RFKILL_TYPE_MAX,
RFKILL_TYPE_UNKNOWN,
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_boxed(NM_DEVICE_SLAVES,
"",
"",
G_TYPE_STRV,
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[IP4_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP4_CONFIG_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_OBJECT,
G_TYPE_OBJECT);
signals[IP6_CONFIG_CHANGED] = g_signal_new(NM_DEVICE_IP6_CONFIG_CHANGED,
G_OBJECT_CLASS_TYPE(object_class),
G_SIGNAL_RUN_FIRST,
0,
NULL,
NULL,
NULL,
G_TYPE_NONE,
2,
G_TYPE_OBJECT,
G_TYPE_OBJECT);
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);
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);
}
/* 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");