summaryrefslogtreecommitdiff
path: root/src/monitor
diff options
context:
space:
mode:
authorJakub Hrozek <jhrozek@redhat.com>2010-06-27 21:22:11 +0200
committerStephen Gallagher <sgallagh@redhat.com>2010-07-09 11:44:07 -0400
commit90acbcf20b5f896ca8f631923afe946c90d90de7 (patch)
treea4942121f0c624d1ea6f16bf093924fade010b88 /src/monitor
parent801fcc63a9ec83d76d8d027758f9a0357b34890f (diff)
downloadsssd-90acbcf20b5f896ca8f631923afe946c90d90de7.tar.gz
sssd-90acbcf20b5f896ca8f631923afe946c90d90de7.tar.bz2
sssd-90acbcf20b5f896ca8f631923afe946c90d90de7.zip
Use netlink to detect going online
Integrates libnl to detect adding routes. When a route is added, the offline status of all back ends is reset. This patch adds no heuristics to detect whether back end went offline. Fixes: #456
Diffstat (limited to 'src/monitor')
-rw-r--r--src/monitor/monitor.c30
-rw-r--r--src/monitor/monitor.h16
-rw-r--r--src/monitor/monitor_interfaces.h1
-rw-r--r--src/monitor/monitor_netlink.c406
4 files changed, 453 insertions, 0 deletions
diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c
index 47832c95..caf40561 100644
--- a/src/monitor/monitor.c
+++ b/src/monitor/monitor.c
@@ -119,6 +119,7 @@ struct mt_ctx {
int service_id_timeout;
bool check_children;
bool services_started;
+ struct netlink_ctx *nlctx;
};
static int start_service(struct mt_svc *mt_svc);
@@ -126,6 +127,7 @@ static int start_service(struct mt_svc *mt_svc);
static int monitor_service_init(struct sbus_connection *conn, void *data);
static int service_send_ping(struct mt_svc *svc);
+static int service_signal_reset_offline(struct mt_svc *svc);
static void ping_check(DBusPendingCall *pending, void *data);
static int service_check_alive(struct mt_svc *svc);
@@ -145,6 +147,23 @@ static int mark_service_as_started(struct mt_svc *svc);
static int monitor_cleanup(void);
+static void network_status_change_cb(enum network_change state,
+ void *cb_data)
+{
+ struct mt_svc *iter;
+ struct mt_ctx *ctx = (struct mt_ctx *) cb_data;
+
+ if (state != NL_ROUTE_UP) return;
+
+ DEBUG(9, ("A new route has appeared, signaling providers to reset offline status\n"));
+ for (iter = ctx->svc_list; iter; iter = iter->next) {
+ /* Don't signal services, only providers */
+ if (iter->provider) {
+ service_signal_reset_offline(iter);
+ }
+ }
+}
+
/* dbus_get_monitor_version
* Return the monitor version over D-BUS */
static int get_monitor_version(DBusMessage *message,
@@ -733,6 +752,10 @@ static int service_signal_offline(struct mt_svc *svc)
{
return service_signal(svc, MON_CLI_METHOD_OFFLINE);
}
+static int service_signal_reset_offline(struct mt_svc *svc)
+{
+ return service_signal(svc, MON_CLI_METHOD_RESET_OFFLINE);
+}
static int service_signal_rotate(struct mt_svc *svc)
{
return service_signal(svc, MON_CLI_METHOD_ROTATE);
@@ -1752,6 +1775,13 @@ int monitor_process_init(struct mt_ctx *ctx,
return ret;
}
+ ret = setup_netlink(ctx, ctx->ev, network_status_change_cb,
+ ctx, &ctx->nlctx);
+ if (ret != EOK) {
+ DEBUG(2, ("Cannot set up listening for network notifications\n"));
+ return ret;
+ }
+
/* start providers */
num_providers = 0;
for (dom = ctx->domains; dom; dom = dom->next) {
diff --git a/src/monitor/monitor.h b/src/monitor/monitor.h
index 54ce3943..73234424 100644
--- a/src/monitor/monitor.h
+++ b/src/monitor/monitor.h
@@ -30,6 +30,8 @@
#define NSCD_SOCKET_PATH "/var/run/nscd/socket"
#endif
+struct config_file_ctx;
+
typedef int (*monitor_reconf_fn) (struct config_file_ctx *file_ctx,
const char *filename);
@@ -38,4 +40,18 @@ struct mt_ctx;
int monitor_process_init(struct mt_ctx *ctx,
const char *config_file);
+/* from monitor_netlink.c */
+struct netlink_ctx;
+
+enum network_change {
+ NL_ROUTE_UP,
+ NL_ROUTE_DOWN
+};
+
+typedef void (*network_change_cb)(enum network_change, void *);
+
+int setup_netlink(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ network_change_cb change_cb, void *cb_data,
+ struct netlink_ctx **_nlctx);
+
#endif /* _MONITOR_H */
diff --git a/src/monitor/monitor_interfaces.h b/src/monitor/monitor_interfaces.h
index 8e334ab7..4830f678 100644
--- a/src/monitor/monitor_interfaces.h
+++ b/src/monitor/monitor_interfaces.h
@@ -41,6 +41,7 @@
#define MON_CLI_METHOD_SHUTDOWN "shutDown"
#define MON_CLI_METHOD_RES_INIT "resInit"
#define MON_CLI_METHOD_OFFLINE "goOffline" /* Applicable only to providers */
+#define MON_CLI_METHOD_RESET_OFFLINE "resetOffline" /* Applicable only to providers */
#define MON_CLI_METHOD_ROTATE "rotateLogs"
#define SSSD_SERVICE_PIPE "private/sbus-monitor"
diff --git a/src/monitor/monitor_netlink.c b/src/monitor/monitor_netlink.c
new file mode 100644
index 00000000..da4b673d
--- /dev/null
+++ b/src/monitor/monitor_netlink.c
@@ -0,0 +1,406 @@
+/*
+ SSSD - Service monitor - netlink support
+
+ Authors:
+ Jakub Hrozek <jhrozek@redhat.com>
+ Parts of this code were borrowed from NetworkManager
+
+ Copyright (C) 2010 Red Hat
+
+ 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 3 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, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <talloc.h>
+#include <tevent.h>
+#include <sys/types.h>
+#define __USE_GNU /* needed for struct ucred */
+#include <sys/socket.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+#include "monitor/monitor.h"
+#include "util/util.h"
+
+#ifdef HAVE_LIBNL
+#include <linux/if.h>
+#include <linux/socket.h>
+#include <linux/rtnetlink.h>
+#include <netlink/netlink.h>
+#include <netlink/utils.h>
+#include <netlink/route/addr.h>
+#include <netlink/route/link.h>
+#include <netlink/route/rtnl.h>
+#include <netlink/handlers.h>
+#endif
+
+/* Linux header file confusion causes this to be undefined. */
+#ifndef SOL_NETLINK
+#define SOL_NETLINK 270
+#endif
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+#define nlw_get_fd nl_handle_get_fd
+#define nlw_recvmsgs_default nl_recvmsgs_def
+#define nlw_get_pid nl_handle_get_pid
+#define nlw_object_match nl_object_match
+#define NLW_OK NL_PROCEED
+#define OBJ_CAST(ptr) ((struct nl_object *) (ptr))
+#else
+#define nlw_get_fd nl_socket_get_fd
+#define nlw_recvmsgs_default nl_recvmsgs_default
+#define nlw_get_pid nl_socket_get_local_port
+#define nlw_object_match nl_object_match_filter
+#define NLW_OK NL_OK
+#endif
+
+struct netlink_ctx {
+#ifdef HAVE_LIBNL
+ struct nl_handle *nlh;
+#endif
+ struct tevent_fd *tefd;
+
+ network_change_cb change_cb;
+ void *cb_data;
+};
+
+#ifdef HAVE_LIBNL
+static int netlink_ctx_destructor(void *ptr)
+{
+ struct netlink_ctx *nlctx;
+ nlctx = talloc_get_type(ptr, struct netlink_ctx);
+
+ nl_handle_destroy(nlctx->nlh);
+ return 0;
+}
+
+/*******************************************************************
+ * Wrappers for different capabilities of different libnl versions
+ *******************************************************************/
+
+static bool nlw_accept_message(struct nl_handle *nlh,
+ const struct sockaddr_nl *snl,
+ struct nlmsghdr *hdr)
+{
+ bool accept_msg = false;
+ uint32_t local_port;
+
+ if (snl == NULL) {
+ DEBUG(3, ("Malformed message, skipping\n"));
+ return false;
+ }
+
+ /* 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 = nlw_get_pid(nlh);
+ if ((hdr->nlmsg_pid == local_port) && snl->nl_groups) {
+ accept_msg = true;
+ }
+
+ if (accept_msg == false) {
+ DEBUG(9, ("ignoring netlink message from PID %d",
+ hdr->nlmsg_pid));
+ }
+
+ return accept_msg;
+}
+
+static bool nlw_is_link_object(struct nl_object *obj)
+{
+ bool is_link_object = true;
+ struct rtnl_link *filter;
+
+ filter = rtnl_link_alloc();
+ if (!filter) {
+ DEBUG(0, ("Allocation error!\n"));
+ is_link_object = false;
+ }
+
+ /* Ensure it's a link object */
+ if (!nlw_object_match(obj, OBJ_CAST(filter))) {
+ DEBUG(2, ("Not a link object\n"));
+ is_link_object = false;
+ }
+
+ rtnl_link_put(filter);
+ return is_link_object;
+}
+
+static int nlw_enable_passcred(struct nl_handle *nlh)
+{
+#ifndef HAVE_NL_SET_PASSCRED
+ return EOK; /* not available in this version */
+#else
+ return nl_set_passcred(nlh, 1); /* 1 = enabled */
+#endif
+}
+
+static int nlw_group_subscribe(struct nl_handle *nlh)
+{
+ int ret;
+
+#ifdef HAVE_NL_SOCKET_ADD_MEMBERSHIP
+ ret = nl_socket_add_membership(nlh, RTNLGRP_LINK);
+ if (ret != 0) {
+ DEBUG(1, ("Unable to add membership: %s\n", nl_geterror()));
+ return ret;
+ }
+#else
+ int nlfd = nlw_get_fd(nlh);
+ int group = RTNLGRP_LINK;
+
+ errno = 0;
+ ret = setsockopt(nlfd, SOL_NETLINK, NETLINK_ADD_MEMBERSHIP,
+ &group, sizeof(group));
+ if (ret < 0) {
+ ret = errno;
+ DEBUG(1, ("setsockopt failed (%d): %s\n", ret, strerror(ret)));
+ return ret;
+ }
+#endif
+
+ return 0;
+}
+
+/*******************************************************************
+ * Callbacks for validating and receiving messages
+ *******************************************************************/
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+static int event_msg_recv(struct sockaddr_nl *nla, struct nlmsghdr *hdr,
+ void *arg)
+{
+ struct netlink_ctx *ctx = (struct netlink_ctx *) arg;
+
+ if (!nlw_accept_message(ctx->nlh, nla, hdr)) {
+ return NL_SKIP;
+ }
+
+ return NLW_OK;
+}
+#else
+static int event_msg_recv(struct nl_msg *msg, void *arg)
+{
+ struct netlink_ctx *ctx = (struct netlink_ctx *) arg;
+ struct nlmsghdr *hdr;
+ const struct sockaddr_nl *snl;
+ struct ucred *creds;
+
+ creds = nlmsg_get_creds(msg);
+ if (!creds || creds->uid != 0) {
+ DEBUG(9, ("Ignoring netlink message from UID %d",
+ creds ? creds->uid : -1));
+ return NL_SKIP;
+ }
+
+ hdr = nlmsg_hdr(msg);
+ snl = nlmsg_get_src(msg);
+
+ if (!nlw_accept_message(ctx->nlh, snl, hdr)) {
+ return NL_SKIP;
+ }
+
+ return NLW_OK;
+}
+#endif
+
+static void link_msg_handler(struct nl_object *obj, void *arg);
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+static int event_msg_ready(struct sockaddr_nl *nla, struct nlmsghdr *hdr,
+ void *arg)
+{
+ nl_msg_parse(hdr, &link_msg_handler, arg);
+ return NLW_OK;
+}
+#else
+static int event_msg_ready(struct nl_msg *msg, void *arg)
+{
+ nl_msg_parse(msg, &link_msg_handler, arg);
+ return NLW_OK;
+}
+#endif
+
+static int nlw_set_callbacks(struct nl_handle *nlh, void *data)
+{
+ int ret = EIO;
+
+#ifndef HAVE_NL_SOCKET_MODIFY_CB
+ struct nl_cb *cb = nl_handle_get_cb(nlh);
+ ret = nl_cb_set(cb, NL_CB_MSG_IN, NL_CB_CUSTOM, event_msg_recv, data);
+#else
+ ret = nl_socket_modify_cb(nlh, NL_CB_MSG_IN, NL_CB_CUSTOM, event_msg_recv, data);
+#endif
+ if (ret != 0) {
+ DEBUG(1, ("Unable to set validation callback\n"));
+ return ret;
+ }
+
+#ifndef HAVE_NL_SOCKET_MODIFY_CB
+ ret = nl_cb_set(cb, NL_CB_VALID, NL_CB_CUSTOM, event_msg_ready, data);
+#else
+ ret = nl_socket_modify_cb(nlh, NL_CB_VALID, NL_CB_CUSTOM, event_msg_ready, data);
+#endif
+ if (ret != 0) {
+ DEBUG(1, ("Unable to set receive callback\n"));
+ return ret;
+ }
+
+ return ret;
+}
+
+static void link_msg_handler(struct nl_object *obj, void *arg)
+{
+ struct netlink_ctx *ctx = (struct netlink_ctx *) arg;
+ struct rtnl_link *link_obj;
+ int flags;
+ int ifidx;
+
+ if (!nlw_is_link_object(obj)) return;
+
+ link_obj = (struct rtnl_link *) obj;
+ flags = rtnl_link_get_flags(link_obj);
+ ifidx = rtnl_link_get_ifindex(link_obj);
+
+ DEBUG(8, ("netlink link message: iface idx %d flags 0x%X\n", ifidx, flags));
+
+ /* IFF_LOWER_UP is the indicator of carrier status */
+ if (flags & IFF_LOWER_UP) {
+ ctx->change_cb(NL_ROUTE_UP, ctx->cb_data);
+ } else {
+ ctx->change_cb(NL_ROUTE_DOWN, ctx->cb_data);
+ }
+}
+
+static void netlink_fd_handler(struct tevent_context *ev, struct tevent_fd *fde,
+ uint16_t flags, void *data)
+{
+ struct netlink_ctx *nlctx = talloc_get_type(data, struct netlink_ctx);
+ int ret;
+
+ if (!nlctx || !nlctx->nlh) {
+ DEBUG(1, ("Invalid netlink handle, this is most likely a bug!\n"));
+ return;
+ }
+
+ ret = nlw_recvmsgs_default(nlctx->nlh);
+ if (ret != EOK) {
+ DEBUG(1, ("Error while reading from netlink fd\n"));
+ return;
+ }
+}
+
+/*******************************************************************
+ * Set up the netlink library
+ *******************************************************************/
+
+int setup_netlink(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ network_change_cb change_cb, void *cb_data,
+ struct netlink_ctx **_nlctx)
+{
+ struct netlink_ctx *nlctx;
+ int ret;
+ int nlfd;
+ unsigned flags;
+
+ nlctx = talloc_zero(mem_ctx, struct netlink_ctx);
+ if (!nlctx) return ENOMEM;
+ talloc_set_destructor((TALLOC_CTX *) nlctx, netlink_ctx_destructor);
+
+ nlctx->change_cb = change_cb;
+ nlctx->cb_data = cb_data;
+
+ /* allocate the libnl handle and register the default filter set */
+ nlctx->nlh = nl_handle_alloc();
+ if (!nlctx->nlh) {
+ DEBUG(1, (("unable to allocate netlink handle: %s"),
+ nl_geterror()));
+ ret = ENOMEM;
+ goto fail;
+ }
+
+ /* Register our custom message validation filter */
+ ret = nlw_set_callbacks(nlctx->nlh, nlctx);
+ if (ret != 0) {
+ DEBUG(1, ("Unable to set callbacks\n"));
+ ret = EIO;
+ goto fail;
+ }
+
+ /* Try to start talking to netlink */
+ ret = nl_connect(nlctx->nlh, NETLINK_ROUTE);
+ if (ret != 0) {
+ DEBUG(1, ("Unable to connect to netlink: %s\n", nl_geterror()));
+ ret = EIO;
+ goto fail;
+ }
+
+ ret = nlw_enable_passcred(nlctx->nlh);
+ if (ret != 0) {
+ DEBUG(1, ("Cannot enable credential passing: %s\n", nl_geterror()));
+ ret = EIO;
+ goto fail;
+ }
+
+ /* Subscribe to the LINK group for internal carrier signals */
+ ret = nlw_group_subscribe(nlctx->nlh);
+ if (ret != 0) {
+ DEBUG(1, ("Unable to subscribe to netlink monitor\n"));
+ ret = EIO;
+ goto fail;
+ }
+
+ nl_disable_sequence_check(nlctx->nlh);
+
+ nlfd = nlw_get_fd(nlctx->nlh);
+ flags = fcntl(nlfd, F_GETFL, 0);
+
+ errno = 0;
+ ret = fcntl(nlfd, F_SETFL, flags | O_NONBLOCK);
+ if (ret < 0) {
+ ret = errno;
+ DEBUG(1, ("Cannot set the netlink fd to nonblocking\n"));
+ goto fail;
+ }
+
+ nlctx->tefd = tevent_add_fd(ev, nlctx, nlfd, TEVENT_FD_READ,
+ netlink_fd_handler, nlctx);
+ if (nlctx->tefd == NULL) {
+ DEBUG(1, ("tevent_add_fd() failed\n"));
+ ret = EIO;
+ goto fail;
+ }
+
+ *_nlctx = nlctx;
+ return EOK;
+
+fail:
+ talloc_free(nlctx);
+ return ret;
+}
+
+#else /* HAVE_LIBNL not defined */
+int setup_netlink(TALLOC_CTX *mem_ctx, struct tevent_context *ev,
+ network_change_cb change_cb, void *cb_data,
+ struct netlink_ctx **_nlctx)
+{
+ if (nlctx) *nlctx = NULL;
+ return EOK;
+}
+#endif