blob: e5b51ced88fb6f8be63f1c99e6a2b80ef650d59c [file] [log] [blame]
/*
* teamd_lw_tipc.c - Team port TIPC link watcher
* Copyright (C) 2014 Erik Hugne <erik.hugne@ericsson.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <sys/queue.h>
#include <sys/poll.h>
#include <linux/tipc.h>
#include <netinet/in.h>
#include <private/misc.h>
#include "teamd.h"
#include "teamd_link_watch.h"
#include "teamd_config.h"
/*
* TIPC monitoring
*/
#define LW_TIPC_TOPSRV_SOCKET "lw_tipc"
struct tipc_link {
LIST_ENTRY(tipc_link) next;
char name[TIPC_MAX_LINK_NAME];
unsigned int peer;
bool up;
};
struct lw_tipc_port_priv {
struct lw_common_port_priv common;
int topsrv_sock;
char bearer[TIPC_MAX_BEARER_NAME];
LIST_HEAD(links, tipc_link) links;
int active_links;
};
static int lw_tipc_load_options(struct teamd_context *ctx,
struct teamd_port *tdport,
struct lw_tipc_port_priv *tipc_ppriv)
{
int err;
const char *tipc_bearer;
struct teamd_config_path_cookie *cpcookie = tipc_ppriv->common.cpcookie;
err = teamd_config_string_get(ctx, &tipc_bearer, "@.tipc_bearer", cpcookie);
if (err)
return -EINVAL;
if (strlen(tipc_bearer) >= TIPC_MAX_BEARER_NAME)
return -EINVAL;
strcpy(tipc_ppriv->bearer, tipc_bearer);
return 0;
}
static bool lw_tipc_topology_check(struct lw_tipc_port_priv *priv,
unsigned int addr)
{
struct tipc_link *l;
LIST_FOREACH(l, &priv->links, next) {
if ((tipc_cluster(l->peer) == tipc_cluster(addr)) && l->up)
return true;
}
return false;
}
static int lw_tipc_link_state_change(struct teamd_context *ctx,
struct tipc_sioc_ln_req *lnr,
struct lw_tipc_port_priv *priv,
bool link_up)
{
bool path_ok;
struct tipc_link *link;
struct lw_common_port_priv *ppriv = (struct lw_common_port_priv *)priv;
LIST_FOREACH(link, &priv->links, next) {
if (strcmp(link->name, lnr->linkname))
continue;
link->up = link_up;
teamd_log_dbg(ctx, "tipc: link <%s> went %s.",
lnr->linkname, link_up ? "up" : "down");
check:
path_ok = lw_tipc_topology_check(priv, link->peer);
return teamd_link_watch_check_link_up(ctx, ppriv->tdport, ppriv,
path_ok);
}
if (!link_up) {
teamd_log_err("tipc: received spurious down event for link <%s>",
lnr->linkname);
return -EINVAL;
}
teamd_log_dbg(ctx, "tipc: established new link <%s>", lnr->linkname);
link = malloc(sizeof(struct tipc_link));
if (!link)
return -ENOMEM;
strcpy(link->name, lnr->linkname);
link->up = link_up;
link->peer = lnr->peer;
LIST_INSERT_HEAD(&priv->links, link, next);
goto check;
}
static int lw_tipc_filter_events(struct lw_tipc_port_priv *tipc_ppriv,
struct tipc_sioc_ln_req *lnr)
{
char name[TIPC_MAX_LINK_NAME];
char needle[24];
char *remote, *bearer;
strcpy(name, lnr->linkname);
sprintf(needle, "-%u.%u.%u:", tipc_zone(lnr->peer),
tipc_cluster(lnr->peer), tipc_node(lnr->peer));
remote = strstr(name, needle);
*(remote++) = '\0';
bearer = strchr(name, ':') + 1;
return strcmp(bearer, tipc_ppriv->bearer);
}
static int lw_tipc_callback_socket(struct teamd_context *ctx, int events, void *priv)
{
int err;
struct lw_tipc_port_priv *tipc_ppriv = priv;
struct tipc_event event;
struct sockaddr_tipc sa;
struct tipc_sioc_ln_req lnr = {0};
err = teamd_recvfrom(tipc_ppriv->topsrv_sock, &event, sizeof(event), 0,
(struct sockaddr *)&sa, sizeof(sa));
if ((err != sizeof(event)) ||
(event.s.seq.type != htonl(TIPC_LINK_STATE)))
goto tipc_cb_err;
lnr.peer = ntohl(event.found_lower);
lnr.bearer_id = ntohl(event.port.ref);
if (ioctl(tipc_ppriv->topsrv_sock, SIOCGETLINKNAME, &lnr) < 0)
goto tipc_cb_err;
if (lw_tipc_filter_events(tipc_ppriv, &lnr))
return 0;
if (event.event == htonl(TIPC_PUBLISHED))
return lw_tipc_link_state_change(ctx, &lnr, tipc_ppriv, true);
else if (event.event == htonl(TIPC_WITHDRAWN))
return lw_tipc_link_state_change(ctx, &lnr, tipc_ppriv, false);
tipc_cb_err:
teamd_log_dbg(ctx, "tipc: link state event error");
return -EINVAL;
}
static int lw_tipc_topsrv_subscribe(struct teamd_context *ctx, struct lw_tipc_port_priv *priv)
{
int err;
struct sockaddr_tipc sa_topsrv = {
.family = AF_TIPC,
.addrtype = TIPC_ADDR_NAME,
.addr.name.name.type = TIPC_TOP_SRV,
.addr.name.name.instance = TIPC_TOP_SRV,
};
struct tipc_subscr sub = {
.seq.type = htonl(TIPC_LINK_STATE),
.seq.lower = htonl(0),
.seq.upper = htonl(~0),
.timeout = htonl(TIPC_WAIT_FOREVER),
.filter = htonl(TIPC_SUB_PORTS),
};
priv->topsrv_sock = socket(AF_TIPC, SOCK_SEQPACKET, 0);
if (priv->topsrv_sock == -1) {
teamd_log_err("Failed to create TIPC socket");
return -errno;
}
err = teamd_loop_callback_fd_add(ctx, LW_TIPC_TOPSRV_SOCKET, priv,
lw_tipc_callback_socket,
priv->topsrv_sock,
TEAMD_LOOP_FD_EVENT_READ);
if (err) {
teamd_log_err("Failed to add socket callback");
err = -errno;
goto close_sock;
}
teamd_loop_callback_enable(ctx, LW_TIPC_TOPSRV_SOCKET, priv);
err = connect(priv->topsrv_sock, (struct sockaddr *) &sa_topsrv, sizeof(sa_topsrv));
if (err < 0) {
teamd_log_err("Failed to connect to TIPC topology server");
err = -errno;
goto close_sock;
}
err = send(priv->topsrv_sock, &sub, sizeof(sub), 0);
if (err != sizeof(sub)) {
teamd_log_err("Failed to subscribe for TIPC link status");
goto close_sock;
}
return 0;
close_sock:
close(priv->topsrv_sock);
return err;
}
static int lw_tipc_port_added(struct teamd_context *ctx,
struct teamd_port *tdport,
void *priv, void *creator_priv)
{
struct lw_tipc_port_priv *tipc_ppriv = priv;
int err;
err = lw_tipc_load_options(ctx, tdport, priv);
if (err) {
teamd_log_err("tipc: Failed to load options");
return err;
}
LIST_INIT(&tipc_ppriv->links);
err = lw_tipc_topsrv_subscribe(ctx, tipc_ppriv);
if (err)
return err;
return 0;
}
static void lw_tipc_port_removed(struct teamd_context *ctx,
struct teamd_port *tdport,
void *priv, void *creator_priv)
{
struct lw_tipc_port_priv *tipc_ppriv = priv;
struct tipc_link *link;
teamd_log_dbg(ctx, "tipc port removed\n");
teamd_loop_callback_del(ctx, LW_TIPC_TOPSRV_SOCKET, priv);
close(tipc_ppriv->topsrv_sock);
while (!LIST_EMPTY(&tipc_ppriv->links)) {
link = LIST_FIRST(&tipc_ppriv->links);
LIST_REMOVE(link, next);
free(link);
}
}
int lw_tipc_state_bearer_get(struct teamd_context *ctx,
struct team_state_gsc *gsc,
void *priv)
{
struct lw_tipc_port_priv *tipc_ppriv = priv;
gsc->data.str_val.ptr = tipc_ppriv->bearer;
return 0;
}
static const struct teamd_state_val lw_tipc_state_vals[] = {
{
.subpath = "tipc_bearer",
.type = TEAMD_STATE_ITEM_TYPE_STRING,
.getter = lw_tipc_state_bearer_get,
},
};
const struct teamd_link_watch teamd_link_watch_tipc = {
.name = "tipc",
.state_vg = {
.vals = lw_tipc_state_vals,
.vals_count = ARRAY_SIZE(lw_tipc_state_vals),
},
.port_priv = {
.init = lw_tipc_port_added,
.fini = lw_tipc_port_removed,
.priv_size = sizeof(struct lw_tipc_port_priv),
},
};