/* -*- Mode: C; tab-width: 4; indent-tabs-mode: t; c-basic-offset: 4 -*- */ /* NetworkManager -- Network link manager * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License along * with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * Copyright (C) 2005 - 2010 Red Hat, Inc. * Copyright (C) 2005 - 2008 Novell, Inc. * Copyright (C) 2005 Ray Strode * * Some code borrowed from HAL: * * Copyright (C) 2003 David Zeuthen, * Copyright (C) 2004 Novell, Inc. */ /* for struct ucred and LIBNL_NEEDS_ADDR_CACHING_WORKAROUND */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "nm-netlink-compat.h" #include "nm-netlink-monitor.h" #include "nm-logging.h" #define EVENT_CONDITIONS ((GIOCondition) (G_IO_IN | G_IO_PRI)) #define ERROR_CONDITIONS ((GIOCondition) (G_IO_ERR | G_IO_NVAL)) #define DISCONNECT_CONDITIONS ((GIOCondition) (G_IO_HUP)) #define NM_NETLINK_MONITOR_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), \ NM_TYPE_NETLINK_MONITOR, \ NMNetlinkMonitorPrivate)) typedef struct { /* Async event listener connection */ struct nl_sock *nlh_event; GIOChannel * io_channel; guint event_id; /* Sync/blocking request/response connection */ struct nl_sock *nlh_sync; struct nl_cache * link_cache; guint request_status_id; GHashTable *subscriptions; } NMNetlinkMonitorPrivate; enum { NOTIFICATION = 0, CARRIER_ON, CARRIER_OFF, LAST_SIGNAL }; static guint signals[LAST_SIGNAL] = { 0 }; G_DEFINE_TYPE (NMNetlinkMonitor, nm_netlink_monitor, G_TYPE_OBJECT); /****************************************************************/ static gboolean detach_monitor (gpointer data) { nm_log_warn (LOGD_HW, "detaching netlink event monitor"); nm_netlink_monitor_detach (NM_NETLINK_MONITOR (data)); return FALSE; } static void log_error_limited (NMNetlinkMonitor *monitor, guint32 code, const char *fmt, ...) { static time_t rl_time = 0; static guint32 rl_code = 0; static guint32 rl_count = 0; va_list args; char *msg; time_t now; g_return_if_fail (monitor != NULL); now = time (NULL); if ((code != rl_code) || (now > rl_time + 10)) { va_start (args, fmt); msg = g_strdup_vprintf (fmt, args); va_end (args); nm_log_warn (LOGD_HW, "error monitoring device for netlink events: %s\n", msg); g_free (msg); rl_time = now; rl_code = code; rl_count = 0; } rl_count++; if (rl_count > 100) { /* Broken drivers will sometimes cause a flood of netlink errors. * rh #459205, novell #443429, lp #284507 */ nm_log_warn (LOGD_HW, "excessive netlink errors ocurred, disabling netlink monitor."); nm_log_warn (LOGD_HW, "link change events will not be processed."); g_idle_add_full (G_PRIORITY_HIGH, detach_monitor, monitor, NULL); } } /****************************************************************/ static void link_msg_handler (struct nl_object *obj, void *arg) { NMNetlinkMonitor *self = NM_NETLINK_MONITOR (arg); struct rtnl_link *filter; struct rtnl_link *link_obj; guint flags; guint ifidx; filter = rtnl_link_alloc (); if (!filter) { log_error_limited (self, NM_NETLINK_MONITOR_ERROR_BAD_ALLOC, _("error processing netlink message: %s"), nl_geterror (ENOMEM)); return; } /* Ensure it's a link object */ if (nl_object_match_filter (obj, OBJ_CAST (filter)) == 0) { rtnl_link_put (filter); return; } link_obj = (struct rtnl_link *) obj; flags = rtnl_link_get_flags (link_obj); ifidx = rtnl_link_get_ifindex (link_obj); nm_log_dbg (LOGD_HW, "netlink link message: iface idx %d flags 0x%X", ifidx, flags); /* IFF_LOWER_UP is the indicator of carrier status since kernel commit * b00055aacdb172c05067612278ba27265fcd05ce in 2.6.17. */ if (flags & IFF_LOWER_UP) g_signal_emit (self, signals[CARRIER_ON], 0, ifidx); else g_signal_emit (self, signals[CARRIER_OFF], 0, ifidx); rtnl_link_put (filter); } static int event_msg_recv (struct nl_msg *msg, void *arg) { struct nl_sock *nlh = arg; struct nlmsghdr *hdr = nlmsg_hdr (msg); struct ucred *creds = nlmsg_get_creds (msg); const struct sockaddr_nl *snl; guint32 local_port; gboolean accept_msg = FALSE; /* Only messages sent from the kernel */ if (!creds || creds->uid != 0) { nm_log_dbg (LOGD_HW, "ignoring netlink message from UID %d", creds ? creds->uid : -1); return NL_SKIP; } snl = nlmsg_get_src (msg); g_assert (snl); /* Accept any messages from the kernel */ if (hdr->nlmsg_pid == 0 || snl->nl_pid == 0) accept_msg = TRUE; /* And any multicast message directed to our netlink PID, since multicast * currently requires CAP_ADMIN to use. */ local_port = nl_socket_get_local_port (nlh); if ((hdr->nlmsg_pid == local_port) && snl->nl_groups) accept_msg = TRUE; if (accept_msg == FALSE) { nm_log_dbg (LOGD_HW, "ignoring netlink message from PID %d (local PID %d, multicast %d)", hdr->nlmsg_pid, local_port, (hdr->nlmsg_flags & NLM_F_MULTI)); return NL_SKIP; } return NL_OK; } static int event_msg_ready (struct nl_msg *msg, void *arg) { NMNetlinkMonitor *self = NM_NETLINK_MONITOR (arg); /* By the time the message gets here we've already checked the sender * and we're sure it's safe to parse this message. */ /* Let clients handle generic messages */ g_signal_emit (self, signals[NOTIFICATION], 0, msg); /* Parse carrier messages */ nl_msg_parse (msg, &link_msg_handler, self); return NL_OK; } static gboolean event_handler (GIOChannel *channel, GIOCondition io_condition, gpointer user_data) { NMNetlinkMonitor *self = (NMNetlinkMonitor *) user_data; NMNetlinkMonitorPrivate *priv; int err; g_return_val_if_fail (NM_IS_NETLINK_MONITOR (self), TRUE); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_return_val_if_fail (priv->event_id > 0, TRUE); if (io_condition & ERROR_CONDITIONS) { const char *err_msg = _("error occurred while waiting for data on socket"); int err_code = 0; socklen_t err_len = sizeof (err_code); /* Grab error information */ if (getsockopt (g_io_channel_unix_get_fd (channel), SOL_SOCKET, SO_ERROR, (void *) &err_code, &err_len) == 0) err_msg = strerror (err_code); log_error_limited (self, NM_NETLINK_MONITOR_ERROR_WAITING_FOR_SOCKET_DATA, "%s", err_msg); return TRUE; } else if (io_condition & DISCONNECT_CONDITIONS) return FALSE; g_return_val_if_fail (!(io_condition & ~EVENT_CONDITIONS), FALSE); /* Process the netlink messages */ err = nl_recvmsgs_default (priv->nlh_event); if (err < 0) { log_error_limited (self, NM_NETLINK_MONITOR_ERROR_PROCESSING_MESSAGE, _("error processing netlink message: %s"), nl_geterror (err)); } return TRUE; } static gboolean nlh_setup (struct nl_sock *nlh, nl_recvmsg_msg_cb_t valid_func, gpointer cb_data, GError **error) { int err; nl_socket_modify_cb (nlh, NL_CB_MSG_IN, NL_CB_CUSTOM, event_msg_recv, cb_data); if (valid_func) nl_socket_modify_cb (nlh, NL_CB_VALID, NL_CB_CUSTOM, valid_func, cb_data); err = nl_connect (nlh, NETLINK_ROUTE); if (err < 0) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_CONNECT, _("unable to connect to netlink for monitoring link status: %s"), nl_geterror (err)); return FALSE; } /* Enable unix socket peer credentials which we use for verifying that the * sender of the message is actually the kernel. */ if (nl_socket_set_passcred (nlh, 1) < 0) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_CONNECT, _("unable to enable netlink handle credential passing: %s"), nl_geterror (err)); return FALSE; } return TRUE; } static gboolean event_connection_setup (NMNetlinkMonitor *self, GError **error) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); GError *channel_error = NULL; GIOFlags channel_flags; int fd; g_return_val_if_fail (priv->io_channel == NULL, FALSE); /* Set up the event listener connection */ priv->nlh_event = nl_socket_alloc (); if (!priv->nlh_event) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_ALLOC_HANDLE, _("unable to allocate netlink handle for monitoring link status: %s"), nl_geterror (ENOMEM)); goto error; } if (!nlh_setup (priv->nlh_event, event_msg_ready, self, error)) goto error; nl_socket_disable_seq_check (priv->nlh_event); /* Subscribe to the LINK group for internal carrier signals */ if (!nm_netlink_monitor_subscribe (self, RTNLGRP_LINK, error)) goto error; fd = nl_socket_get_fd (priv->nlh_event); priv->io_channel = g_io_channel_unix_new (fd); g_io_channel_set_encoding (priv->io_channel, NULL, &channel_error); /* Encoding is NULL, so no conversion error can possibly occur */ g_assert (channel_error == NULL); g_io_channel_set_close_on_unref (priv->io_channel, TRUE); channel_flags = g_io_channel_get_flags (priv->io_channel); channel_error = NULL; g_io_channel_set_flags (priv->io_channel, channel_flags | G_IO_FLAG_NONBLOCK, &channel_error); if (channel_error != NULL) { g_propagate_error (error, channel_error); goto error; } return TRUE; error: if (priv->io_channel) nm_netlink_monitor_close_connection (self); if (priv->nlh_event) { nl_socket_free (priv->nlh_event); priv->nlh_event = NULL; } return FALSE; } static gboolean sync_connection_setup (NMNetlinkMonitor *self, GError **error) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); #ifdef LIBNL_NEEDS_ADDR_CACHING_WORKAROUND struct nl_cache *addr_cache; #endif int err; /* Set up the event listener connection */ priv->nlh_sync = nl_socket_alloc (); if (!priv->nlh_sync) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_ALLOC_HANDLE, _("unable to allocate netlink handle for monitoring link status: %s"), nl_geterror (ENOMEM)); goto error; } if (!nlh_setup (priv->nlh_sync, NULL, self, error)) goto error; #ifdef LIBNL_NEEDS_ADDR_CACHING_WORKAROUND /* Work around apparent libnl bug; rtnl_addr requires that all * addresses have the "peer" attribute set in order to be compared * for equality, but this attribute is not normally set. As a * result, most addresses will not compare as equal even to * themselves, busting caching. */ rtnl_addr_alloc_cache (priv->nlh_sync, &addr_cache); g_warn_if_fail (addr_cache != NULL); nl_cache_get_ops (addr_cache)->co_obj_ops->oo_id_attrs &= ~0x80; nl_cache_free (addr_cache); #endif err = rtnl_link_alloc_cache (priv->nlh_sync, &priv->link_cache); if (err < 0) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_ALLOC_LINK_CACHE, _("unable to allocate netlink link cache for monitoring link status: %s"), nl_geterror (err)); goto error; } nl_cache_mngt_provide (priv->link_cache); return TRUE; error: if (priv->link_cache) { nl_cache_free (priv->link_cache); priv->link_cache = NULL; } if (priv->nlh_sync) { nl_socket_free (priv->nlh_sync); priv->nlh_sync = NULL; } return FALSE; } gboolean nm_netlink_monitor_open_connection (NMNetlinkMonitor *self, GError **error) { g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (NM_IS_NETLINK_MONITOR (self), FALSE); if (!event_connection_setup (self, error)) return FALSE; if (!sync_connection_setup (self, error)) return FALSE; return TRUE; } void nm_netlink_monitor_close_connection (NMNetlinkMonitor *self) { NMNetlinkMonitorPrivate *priv; g_return_if_fail (NM_IS_NETLINK_MONITOR (self)); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_return_if_fail (priv->io_channel != NULL); if (priv->event_id) nm_netlink_monitor_detach (self); g_io_channel_shutdown (priv->io_channel, TRUE /* flush pending data */, NULL); g_io_channel_unref (priv->io_channel); priv->io_channel = NULL; } void nm_netlink_monitor_attach (NMNetlinkMonitor *self) { NMNetlinkMonitorPrivate *priv; g_return_if_fail (NM_IS_NETLINK_MONITOR (self)); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_return_if_fail (priv->nlh_event != NULL); g_return_if_fail (priv->event_id == 0); priv->event_id = g_io_add_watch (priv->io_channel, (EVENT_CONDITIONS | ERROR_CONDITIONS | DISCONNECT_CONDITIONS), event_handler, self); } void nm_netlink_monitor_detach (NMNetlinkMonitor *self) { NMNetlinkMonitorPrivate *priv; g_return_if_fail (NM_IS_NETLINK_MONITOR (self)); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_return_if_fail (priv->event_id > 0); g_source_remove (priv->event_id); priv->event_id = 0; } static int get_subs (NMNetlinkMonitor *self, int group) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); return GPOINTER_TO_INT (g_hash_table_lookup (priv->subscriptions, GINT_TO_POINTER (group))); } static void set_subs (NMNetlinkMonitor *self, int group, int new_subs) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_hash_table_insert (priv->subscriptions, GINT_TO_POINTER (group), GINT_TO_POINTER (new_subs)); } gboolean nm_netlink_monitor_subscribe (NMNetlinkMonitor *self, int group, GError **error) { NMNetlinkMonitorPrivate *priv; int subs, err; g_return_val_if_fail (NM_IS_NETLINK_MONITOR (self), FALSE); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); if (!priv->nlh_event) { if (!nm_netlink_monitor_open_connection (self, error)) return FALSE; } subs = get_subs (self, group) + 1; if (subs == 1) { err = nl_socket_add_membership (priv->nlh_event, group); if (err < 0) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_NETLINK_JOIN_GROUP, _("unable to join netlink group: %s"), nl_geterror (err)); return FALSE; } } /* Update # of subscriptions for this group */ set_subs (self, group, subs); return TRUE; } void nm_netlink_monitor_unsubscribe (NMNetlinkMonitor *self, int group) { NMNetlinkMonitorPrivate *priv; int subs; g_return_if_fail (self != NULL); g_return_if_fail (NM_IS_NETLINK_MONITOR (self)); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); g_return_if_fail (priv->nlh_event != NULL); subs = get_subs (self, group) - 1; if (subs == 0) nl_socket_drop_membership (priv->nlh_event, group); /* Update # of subscriptions for this group */ set_subs (self, group, subs); } /***************************************************************/ gboolean nm_netlink_monitor_request_ip6_info (NMNetlinkMonitor *self, GError **error) { NMNetlinkMonitorPrivate *priv; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (NM_IS_NETLINK_MONITOR (self), FALSE); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); /* FIXME: nl_rtgen_request() gets the return value screwed up with * libnl-1.1; revisit this and return a proper error when we port to * a later libnl. */ nl_rtgen_request (priv->nlh_event, RTM_GETLINK, AF_INET6, NLM_F_DUMP); return TRUE; } static gboolean deferred_emit_carrier_state (gpointer user_data) { NMNetlinkMonitor *self = NM_NETLINK_MONITOR (user_data); NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); int err; priv->request_status_id = 0; /* Update the link cache with latest state, and if there are no errors * emit the link states for all the interfaces in the cache. */ err = nl_cache_refill (priv->nlh_sync, priv->link_cache); if (err < 0) nm_log_err (LOGD_HW, "error updating link cache: %s", nl_geterror (err)); else nl_cache_foreach_filter (priv->link_cache, NULL, link_msg_handler, self); return FALSE; } void nm_netlink_monitor_request_status (NMNetlinkMonitor *self) { NMNetlinkMonitorPrivate *priv; g_return_if_fail (NM_IS_NETLINK_MONITOR (self)); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); /* Schedule the carrier state emission */ if (!priv->request_status_id) priv->request_status_id = g_idle_add (deferred_emit_carrier_state, self); } typedef struct { NMNetlinkMonitor *self; struct rtnl_link *filter; GError *error; guint32 flags; } GetFlagsInfo; static void get_flags_sync_cb (struct nl_object *obj, void *arg) { GetFlagsInfo *info = arg; /* Ensure this cache item matches our filter */ if (nl_object_match_filter (obj, OBJ_CAST (info->filter)) != 0) info->flags = rtnl_link_get_flags ((struct rtnl_link *) obj); } gboolean nm_netlink_monitor_get_flags_sync (NMNetlinkMonitor *self, guint32 ifindex, guint32 *ifflags, GError **error) { NMNetlinkMonitorPrivate *priv; GetFlagsInfo info; struct rtnl_link *filter; int err; g_return_val_if_fail (self != NULL, FALSE); g_return_val_if_fail (NM_IS_NETLINK_MONITOR (self), FALSE); g_return_val_if_fail (ifflags != NULL, FALSE); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); /* Update the link cache with the latest information */ err = nl_cache_refill (priv->nlh_sync, priv->link_cache); if (err < 0) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_LINK_CACHE_UPDATE, _("error updating link cache: %s"), nl_geterror (err)); return FALSE; } /* HACK: Apparently to get it working we have to refill the cache twice; * otherwise some kernels (or maybe libnl?) only send a few of the * interfaces in the refill request. */ if (nl_cache_refill (priv->nlh_sync, priv->link_cache)) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_LINK_CACHE_UPDATE, _("error updating link cache: %s"), nl_geterror (err)); return FALSE; } /* Set up the filter */ filter = rtnl_link_alloc (); if (!filter) { g_set_error (error, NM_NETLINK_MONITOR_ERROR, NM_NETLINK_MONITOR_ERROR_BAD_ALLOC, _("error processing netlink message: %s"), nl_geterror (err)); return FALSE; } rtnl_link_set_ifindex (filter, ifindex); memset (&info, 0, sizeof (info)); info.self = self; info.filter = filter; info.error = NULL; nl_cache_foreach_filter (priv->link_cache, NULL, get_flags_sync_cb, &info); rtnl_link_put (filter); if (info.error) { if (error) *error = info.error; else g_error_free (info.error); return FALSE; } else *ifflags = info.flags; return TRUE; /* success */ } /***************************************************************/ struct nl_sock * nm_netlink_get_default_handle (void) { NMNetlinkMonitor *self; struct nl_sock *nlh; self = nm_netlink_monitor_get (); nlh = NM_NETLINK_MONITOR_GET_PRIVATE (self)->nlh_sync; g_object_unref (self); return nlh; } int nm_netlink_iface_to_index (const char *iface) { NMNetlinkMonitor *self; NMNetlinkMonitorPrivate *priv; int idx; g_return_val_if_fail (iface != NULL, -1); self = nm_netlink_monitor_get (); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); nl_cache_refill (priv->nlh_sync, priv->link_cache); idx = rtnl_link_name2i (priv->link_cache, iface); g_object_unref (self); return idx; } /** * nm_netlink_index_to_iface: * @idx: kernel interface index * * Returns: the device name corresponding to the kernel interface index; caller * owns returned value and must free it when it is no longer required **/ #define MAX_IFACE_LEN 33 char * nm_netlink_index_to_iface (int idx) { NMNetlinkMonitor *self; NMNetlinkMonitorPrivate *priv; char *buf = NULL; g_return_val_if_fail (idx >= 0, NULL); self = nm_netlink_monitor_get (); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); buf = g_malloc0 (MAX_IFACE_LEN); g_assert (buf); nl_cache_refill (priv->nlh_sync, priv->link_cache); if (!rtnl_link_i2name (priv->link_cache, idx, buf, MAX_IFACE_LEN - 1)) { nm_log_warn (LOGD_HW, "(%d) failed to find interface name for index", idx); g_free (buf); buf = NULL; } g_object_unref (self); return buf; } struct rtnl_link * nm_netlink_index_to_rtnl_link (int idx) { NMNetlinkMonitor *self; NMNetlinkMonitorPrivate *priv; struct rtnl_link *ret = NULL; if (idx <= 0) return NULL; self = nm_netlink_monitor_get (); priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); nl_cache_refill (priv->nlh_sync, priv->link_cache); ret = rtnl_link_get (priv->link_cache, idx); g_object_unref (self); return ret; } /***************************************************************/ NMNetlinkMonitor * nm_netlink_monitor_get (void) { static NMNetlinkMonitor *singleton = NULL; GError *error = NULL; if (!singleton) { singleton = (NMNetlinkMonitor *) g_object_new (NM_TYPE_NETLINK_MONITOR, NULL); g_return_val_if_fail (singleton != NULL, NULL); if (nm_netlink_monitor_open_connection (singleton, &error)) { nm_netlink_monitor_attach (singleton); /* Request initial status of cards */ nm_netlink_monitor_request_status (singleton); } else { nm_log_warn (LOGD_HW, "Failed to connect to netlink: (%d) %s", error ? error->code : 0, (error && error->message) ? error->message : "(unknown)"); g_clear_error (&error); g_object_unref (singleton); singleton = NULL; } } else g_object_ref (singleton); return singleton; } static void nm_netlink_monitor_init (NMNetlinkMonitor *self) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (self); priv->subscriptions = g_hash_table_new (g_direct_hash, g_direct_equal); } static void finalize (GObject *object) { NMNetlinkMonitorPrivate *priv = NM_NETLINK_MONITOR_GET_PRIVATE (object); if (priv->request_status_id) g_source_remove (priv->request_status_id); if (priv->io_channel) nm_netlink_monitor_close_connection (NM_NETLINK_MONITOR (object)); if (priv->link_cache) { nl_cache_free (priv->link_cache); priv->link_cache = NULL; } if (priv->nlh_event) { nl_socket_free (priv->nlh_event); priv->nlh_event = NULL; } if (priv->nlh_sync) { nl_socket_free (priv->nlh_sync); priv->nlh_sync = NULL; } g_hash_table_destroy (priv->subscriptions); G_OBJECT_CLASS (nm_netlink_monitor_parent_class)->finalize (object); } static void nm_netlink_monitor_class_init (NMNetlinkMonitorClass *monitor_class) { GObjectClass *object_class = G_OBJECT_CLASS (monitor_class); g_type_class_add_private (monitor_class, sizeof (NMNetlinkMonitorPrivate)); /* Virtual methods */ object_class->finalize = finalize; /* Signals */ signals[NOTIFICATION] = g_signal_new ("notification", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NMNetlinkMonitorClass, notification), NULL, NULL, g_cclosure_marshal_VOID__POINTER, G_TYPE_NONE, 1, G_TYPE_POINTER); signals[CARRIER_ON] = g_signal_new ("carrier-on", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NMNetlinkMonitorClass, carrier_on), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); signals[CARRIER_OFF] = g_signal_new ("carrier-off", G_OBJECT_CLASS_TYPE (object_class), G_SIGNAL_RUN_LAST, G_STRUCT_OFFSET (NMNetlinkMonitorClass, carrier_off), NULL, NULL, g_cclosure_marshal_VOID__INT, G_TYPE_NONE, 1, G_TYPE_INT); } GQuark nm_netlink_monitor_error_quark (void) { static GQuark error_quark = 0; if (G_UNLIKELY (error_quark == 0)) error_quark = g_quark_from_static_string ("nm-netlink-monitor-error-quark"); return error_quark; }