| /* |
| * 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), |
| }, |
| }; |