From 7f2cde2e0702fddd51a7101a8ae81c2419f5bbf9 Mon Sep 17 00:00:00 2001
From: Jakub Hrozek <jhrozek@redhat.com>
Date: Sun, 27 Jun 2010 21:22:11 +0200
Subject: [PATCH] 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
---
 src/Makefile.am                  |    5 +-
 src/conf_macros.m4               |   16 ++
 src/configure.ac                 |    6 +
 src/external/libnl.m4            |   21 ++
 src/monitor/monitor.c            |   30 +++
 src/monitor/monitor.h            |   16 ++
 src/monitor/monitor_interfaces.h |    1 +
 src/monitor/monitor_netlink.c    |  426 ++++++++++++++++++++++++++++++++++++++
 src/providers/data_provider_be.c |   20 ++
 9 files changed, 540 insertions(+), 1 deletions(-)
 create mode 100644 src/external/libnl.m4
 create mode 100644 src/monitor/monitor_netlink.c

diff --git a/src/Makefile.am b/src/Makefile.am
index 67b51f4..1618682 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -202,6 +202,7 @@ AM_CPPFLAGS = -Wall \
     $(COLLECTION_CFLAGS) \
     $(INI_CFG_CFLAGS) \
     $(DHASH_CFLAGS) \
+    $(LIBNL_CFLAGS) \
     -DLIBDIR=\"$(libdir)\" \
     -DVARDIR=\"$(localstatedir)\" \
     -DSHLIBEXT=\"$(SHLIBEXT)\" \
@@ -373,10 +374,12 @@ dist_noinst_HEADERS = \
 ####################
 sssd_SOURCES = \
     monitor/monitor.c \
+    monitor/monitor_netlink.c \
     confdb/confdb_setup.c \
     $(SSSD_UTIL_OBJ)
 sssd_LDADD = \
-    $(SSSD_LIBS)
+    $(SSSD_LIBS) \
+    $(LIBNL_LIBS)
 
 sssd_nss_SOURCES = \
     responder/nss/nsssrv.c \
diff --git a/src/conf_macros.m4 b/src/conf_macros.m4
index 65de7fb..cb4b325 100644
--- a/src/conf_macros.m4
+++ b/src/conf_macros.m4
@@ -233,3 +233,19 @@ AC_DEFUN([WITH_SEMANAGE],
     fi
     AM_CONDITIONAL([BUILD_SEMANAGE], [test x"$with_semanage" = xyes])
   ])
+
+AC_DEFUN([WITH_LIBNL],
+  [ AC_ARG_WITH([libnl],
+                [AC_HELP_STRING([--with-libnl],
+                                [Whether to build with libnetlink support [yes]]
+                               )
+                ],
+                [],
+                with_libnl=yes
+               )
+    if test x"$with_libnl" = xyes; then
+        HAVE_LIBNL=1
+        AC_SUBST(HAVE_LIBNL)
+        AC_DEFINE_UNQUOTED(HAVE_LIBNL, 1, [Build with libnetlink support])
+    fi
+  ])
diff --git a/src/configure.ac b/src/configure.ac
index 811c2d9..8a2a95f 100644
--- a/src/configure.ac
+++ b/src/configure.ac
@@ -72,6 +72,7 @@ WITH_PYTHON_BINDINGS
 WITH_SELINUX
 WITH_NSCD
 WITH_SEMANAGE
+WITH_LIBNL
 
 m4_include([external/platform.m4])
 m4_include([external/pkg.m4])
@@ -96,6 +97,7 @@ m4_include([external/crypto.m4])
 m4_include([external/nscd.m4])
 m4_include([external/nsupdate.m4])
 m4_include([external/libkeyutils.m4])
+m4_include([external/libnl.m4])
 m4_include([util/signal.m4])
 
 PKG_CHECK_MODULES([DBUS],[dbus-1])
@@ -136,6 +138,10 @@ if test x$HAVE_SEMANAGE != x -a x$HAVE_SELINUX != x; then
     AM_CHECK_SEMANAGE
 fi
 
+if test x$HAVE_LIBNL != x; then
+    AM_CHECK_LIBNL
+fi
+
 AC_CHECK_HEADERS([sys/inotify.h])
 
 AC_CHECK_HEADERS([sasl/sasl.h],,AC_MSG_ERROR([Could not find SASL headers]))
diff --git a/src/external/libnl.m4 b/src/external/libnl.m4
new file mode 100644
index 0000000..563d7c3
--- /dev/null
+++ b/src/external/libnl.m4
@@ -0,0 +1,21 @@
+dnl A macro to check the availability and version of libnetlink
+AC_DEFUN([AM_CHECK_LIBNL],
+[
+    PKG_CHECK_MODULES(libnl, libnl-1)
+
+    AC_CHECK_HEADERS(netlink.h)
+    AC_CHECK_LIB(nl, nl_connect, [ LIBNL_LIBS="-lnl" ], [AC_MSG_ERROR([libnl is required])])
+
+    PKG_CHECK_EXISTS(libnl-1 >= 1.1,
+                     [AC_MSG_NOTICE([libnl version is 1.1 or higher])
+                      AC_DEFINE([HAVE_LIBNL_AT_LEAST_1_1],
+                                 1,
+                                 [Define if libnl version is 1.1 or newer])],
+                     [AC_MSG_NOTICE([libnl version is older than 1.1])
+                      AC_DEFINE([HAVE_LIBNL_OLDER_THAN_1_1],
+                                 1,
+                                 [Define if libnl version is older than 1.1])])
+
+    AC_SUBST(LIBNL_CFLAGS)
+    AC_SUBST(LIBNL_LIBS)
+])
diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c
index 41eadf5..d7d6687 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);
@@ -1735,6 +1758,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 54ce394..7323442 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 8e334ab..4830f67 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 0000000..0981dd7
--- /dev/null
+++ b/src/monitor/monitor_netlink.c
@@ -0,0 +1,426 @@
+/*
+   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)
+{
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+    return EOK;                      /* not available in this version */
+#else
+    return nl_set_passcred(nlh, 1);  /* 1 = enabled */
+#endif
+}
+
+static struct nl_handle *nlw_alloc_netlink_handle(void)
+{
+    struct nl_handle *nlh = NULL;
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+    nlh = nl_handle_alloc();
+#else
+    struct nl_cb *cb;
+
+    cb = nl_cb_alloc(NL_CB_DEFAULT);
+    if (!cb) {
+        return NULL;
+    }
+
+    nlh = nl_handle_alloc_cb(cb);
+    nl_cb_put(cb);
+#endif
+
+    return nlh;
+}
+
+static int nlw_group_subscribe(struct nl_handle *nlh)
+{
+    int ret;
+
+#ifdef HAVE_LIBNL_AT_LEAST_1_1
+    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;
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+    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;
+    }
+
+#ifdef HAVE_LIBNL_OLDER_THAN_1_1
+    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 = nlw_alloc_netlink_handle();
+    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)
+{
+    return EOK;
+}
+#endif
diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c
index 059a051..004ee09 100644
--- a/src/providers/data_provider_be.c
+++ b/src/providers/data_provider_be.c
@@ -55,11 +55,14 @@ static int data_provider_res_init(DBusMessage *message,
                                   struct sbus_connection *conn);
 static int data_provider_go_offline(DBusMessage *message,
                                     struct sbus_connection *conn);
+static int data_provider_reset_offline(DBusMessage *message,
+                                       struct sbus_connection *conn);
 
 struct sbus_method monitor_be_methods[] = {
     { MON_CLI_METHOD_PING, monitor_common_pong },
     { MON_CLI_METHOD_RES_INIT, data_provider_res_init },
     { MON_CLI_METHOD_OFFLINE, data_provider_go_offline },
+    { MON_CLI_METHOD_RESET_OFFLINE, data_provider_reset_offline },
     { MON_CLI_METHOD_ROTATE, monitor_common_rotate_logs },
     { NULL, NULL }
 };
@@ -170,6 +173,14 @@ void be_mark_offline(struct be_ctx *ctx)
     be_run_offline_cb(ctx);
 }
 
+void be_reset_offline(struct be_ctx *ctx)
+{
+    DEBUG(8, ("Going back online!\n"));
+
+    ctx->offstat.offline = false;
+    be_run_online_cb(ctx);
+}
+
 static int be_check_online(DBusMessage *message, struct sbus_connection *conn)
 {
     struct be_client *becli;
@@ -1229,3 +1240,12 @@ static int data_provider_go_offline(DBusMessage *message,
     be_mark_offline(be_ctx);
     return monitor_common_pong(message, conn);
 }
+
+static int data_provider_reset_offline(DBusMessage *message,
+                                       struct sbus_connection *conn)
+{
+    struct be_ctx *be_ctx;
+    be_ctx = talloc_get_type(sbus_conn_get_private_data(conn), struct be_ctx);
+    be_reset_offline(be_ctx);
+    return monitor_common_pong(message, conn);
+}
-- 
1.6.6.1

