diff options
author | Jakub Hrozek <jhrozek@redhat.com> | 2010-06-27 21:22:11 +0200 |
---|---|---|
committer | Stephen Gallagher <sgallagh@redhat.com> | 2010-07-09 11:44:07 -0400 |
commit | 90acbcf20b5f896ca8f631923afe946c90d90de7 (patch) | |
tree | a4942121f0c624d1ea6f16bf093924fade010b88 /src | |
parent | 801fcc63a9ec83d76d8d027758f9a0357b34890f (diff) | |
download | sssd-90acbcf20b5f896ca8f631923afe946c90d90de7.tar.gz sssd-90acbcf20b5f896ca8f631923afe946c90d90de7.tar.xz 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')
-rw-r--r-- | src/Makefile.am | 5 | ||||
-rw-r--r-- | src/conf_macros.m4 | 16 | ||||
-rw-r--r-- | src/configure.ac | 6 | ||||
-rw-r--r-- | src/external/libnl.m4 | 36 | ||||
-rw-r--r-- | src/monitor/monitor.c | 30 | ||||
-rw-r--r-- | src/monitor/monitor.h | 16 | ||||
-rw-r--r-- | src/monitor/monitor_interfaces.h | 1 | ||||
-rw-r--r-- | src/monitor/monitor_netlink.c | 406 | ||||
-rw-r--r-- | src/providers/data_provider_be.c | 20 |
9 files changed, 535 insertions, 1 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index c80409c87..64e0e081a 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)\" \ @@ -374,10 +375,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 65de7fbdb..cb4b32557 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 811c2d9c4..8a2a95f82 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 000000000..ed8f95b63 --- /dev/null +++ b/src/external/libnl.m4 @@ -0,0 +1,36 @@ +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])]) + + AC_CHECK_LIB([nl], + [nl_handle_get_fd], + [AC_DEFINE([HAVE_LIBNL_OLDER_THAN_1_1], 1, [Does libnl have pre-1.1 API?]) + ], + ) + + dnl Check if this particular version of libnl supports particular functions + AC_CHECK_LIB([nl], + [nl_socket_add_membership], + [AC_DEFINE([HAVE_NL_SOCKET_ADD_MEMBERSHIP], 1, [Does libnl have nl_socket_add_membership?]) + ], + ) + + AC_CHECK_LIB([nl], + [nl_socket_modify_cb], + [AC_DEFINE([HAVE_NL_SOCKET_MODIFY_CB], 1, [Does libnl have nl_socket_modify_cb?]) + ], + ) + + AC_CHECK_LIB([nl], + [nl_set_passcred], + [AC_DEFINE([HAVE_NL_SET_PASSCRED], 1, [Does libnl have nl_set_passcred?]) + ], + ) + + AC_SUBST(LIBNL_CFLAGS) + AC_SUBST(LIBNL_LIBS) +]) diff --git a/src/monitor/monitor.c b/src/monitor/monitor.c index 47832c95d..caf405612 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 54ce39431..73234424f 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 8e334ab73..4830f678f 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 000000000..da4b673d5 --- /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 diff --git a/src/providers/data_provider_be.c b/src/providers/data_provider_be.c index 059a0514e..004ee0973 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); +} |