/
githubmirror
/
libteam
Обзор
Документация
Войти
/
githubmirror
/
libteam
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
master
teamd/teamd_runner_lacp.c
1 974 строки
52 KB
Jiri Pirko
Revert: teamd: lacp: make sure that lacp_port_agg_update() works with correct unselectable state
02 фев 2023, 19:47
02 фев 2023, 19:47
61e2781
Код
Авторство
О чём код?
/* * teamd_runner_lacp.c - Teamd 802.3ad LACP runner implementation * Copyright (C) 2012-2015 Jiri Pirko <jiri@resnulli.us> * * 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 <stdbool.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <limits.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <netinet/in.h> #include <errno.h> #include <team.h> #include <private/misc.h> #include <net/ethernet.h> #include "teamd.h" #include "teamd_config.h" #include "teamd_state.h" #include "teamd_workq.h" /* * Packet format for LACPDU described in * IEEE Std 802.3ad-2000 (43.4.2.2) */ #define INFO_STATE_LACP_ACTIVITY 0x01 #define INFO_STATE_LACP_TIMEOUT 0x02 #define INFO_STATE_AGGREGATION 0x04 #define INFO_STATE_SYNCHRONIZATION 0x08 #define INFO_STATE_COLLECTING 0x10 #define INFO_STATE_DISTRIBUTING 0x20 #define INFO_STATE_DEFAULTED 0x40 #define INFO_STATE_EXPIRED 0x80 struct lacpdu_info { uint16_t system_priority; uint8_t system[ETH_ALEN]; /* ID */ uint16_t key; uint16_t port_priority; uint16_t port; /* ID */ uint8_t state; } __attribute__((__packed__)); struct lacpdu { struct ether_header hdr; uint8_t subtype; uint8_t version_number; uint8_t actor_tlv_type; uint8_t actor_info_len; struct lacpdu_info actor; uint8_t __reserved1[3]; uint8_t partner_tlv_type; uint8_t partner_info_len; struct lacpdu_info partner; uint8_t __reserved2[3]; uint8_t collector_tlv_type; uint8_t collector_info_len; uint16_t collector_max_delay; uint8_t __reserved3[12]; uint8_t terminator_tlv_type; uint8_t terminator_info_len; uint8_t __reserved4[50]; } __attribute__((__packed__)); static void lacpdu_init(struct lacpdu *lacpdu) { memset(lacpdu, 0, sizeof(*lacpdu)); lacpdu->subtype = 0x01; lacpdu->version_number = 0x01; lacpdu->actor_tlv_type = 0x01; lacpdu->actor_info_len = 0x14; lacpdu->partner_tlv_type = 0x02; lacpdu->partner_info_len = 0x14; lacpdu->collector_tlv_type = 0x03; lacpdu->collector_info_len = 0x10; } static bool lacpdu_check(struct lacpdu *lacpdu) { /* * According to Annex 43B, section 4, aside from LACP, the Slow * Protocol linktype is also to be used by other protocols, like * 0x02 for LAMP, 0x03 for OAM. So for none LACP protocols, just * silence ignore. */ if (lacpdu->subtype != 0x01) return false; /* * According to 43.4.12 version_number, tlv_type and reserved fields * should not be checked. */ if (lacpdu->actor_info_len != 0x14 || lacpdu->partner_info_len != 0x14 || lacpdu->collector_info_len != 0x10 || lacpdu->terminator_info_len != 0x00) { teamd_log_warn("malformed LACP PDU came."); return false; } return true; } enum lacp_agg_select_policy { LACP_AGG_SELECT_LACP_PRIO = 0, LACP_AGG_SELECT_LACP_PRIO_STABLE = 1, LACP_AGG_SELECT_BANDWIDTH = 2, LACP_AGG_SELECT_COUNT = 3, LACP_AGG_SELECT_PORT_CONFIG = 4, }; static const char *lacp_agg_select_policy_names_list[] = { "lacp_prio", "lacp_prio_stable", "bandwidth", "count", "port_config", }; #define LACP_AGG_SELECT_POLICY_NAMES_LIST_SIZE \ ARRAY_SIZE(lacp_agg_select_policy_names_list) struct lacp_port; struct lacp { struct teamd_context *ctx; struct lacp_port *selected_agg_lead; /* leading port of selected aggregator */ bool carrier_up; struct { bool active; #define LACP_CFG_DFLT_ACTIVE true uint16_t sys_prio; #define LACP_CFG_DFLT_SYS_PRIO 0xffff bool fast_rate; #define LACP_CFG_DFLT_FAST_RATE false int min_ports; #define LACP_CFG_DFLT_MIN_PORTS 1 #define LACP_CFG_DFLT_MIN_PORTS_MAX 1024 enum lacp_agg_select_policy agg_select_policy; #define LACP_CFG_DFLT_AGG_SELECT_POLICY LACP_AGG_SELECT_LACP_PRIO } cfg; struct teamd_balancer *tb; }; enum lacp_port_state { PORT_STATE_DISABLED = 0, PORT_STATE_CURRENT = 1, PORT_STATE_EXPIRED = 2, PORT_STATE_DEFAULTED = 3, }; static const char *lacp_port_state_name[] = { "disabled", "current", "expired", "defaulted", }; struct lacp_port { struct teamd_context *ctx; struct teamd_port *tdport; struct lacp *lacp; int sock; struct lacpdu_info actor; struct lacpdu_info partner; struct lacpdu_info __partner_last; /* last state before update */ bool periodic_on; struct lacp_port *agg_lead; /* leading port of aggregator. * NULL in case this port is not selected */ enum lacp_port_state state; struct { uint32_t speed; uint8_t duplex; bool up; } __link_last; struct { uint16_t lacp_prio; #define LACP_PORT_CFG_DFLT_LACP_PRIO 0xff uint16_t lacp_key; #define LACP_PORT_CFG_DFLT_LACP_KEY 0 bool sticky; #define LACP_PORT_CFG_DFLT_STICKY false } cfg; }; static struct lacp_port *lacp_port_get(struct lacp *lacp, struct teamd_port *tdport) { /* * When calling this after teamd_event_watch_register() which is in * lacp_init() it is ensured that this will always return valid priv * pointer for an existing port. */ return teamd_get_first_port_priv_by_creator(tdport, lacp); } static uint32_t lacp_agg_id(struct lacp_port *agg_lead) { return agg_lead ? agg_lead->tdport->ifindex : 0; } static bool lacp_agg_selected(struct lacp_port *agg_lead) { return agg_lead ? agg_lead == agg_lead->lacp->selected_agg_lead : false; } static uint32_t lacp_port_agg_id(struct lacp_port *lacp_port) { return lacp_agg_id(lacp_port->agg_lead); } static uint32_t lacp_port_agg_selected(struct lacp_port *lacp_port) { return lacp_agg_selected(lacp_port->agg_lead); } static bool lacp_port_is_agg_lead(struct lacp_port *lacp_port) { return lacp_port->agg_lead == lacp_port; } static const char *lacp_get_agg_select_policy_name(struct lacp *lacp) { return lacp_agg_select_policy_names_list[lacp->cfg.agg_select_policy]; } static int lacp_assign_agg_select_policy(struct lacp *lacp, const char *agg_select_policy_name) { int i = LACP_CFG_DFLT_AGG_SELECT_POLICY; if (!agg_select_policy_name) goto found; for (i = 0; i < LACP_AGG_SELECT_POLICY_NAMES_LIST_SIZE; i++) if (!strcmp(lacp_agg_select_policy_names_list[i], agg_select_policy_name)) goto found; return -ENOENT; found: lacp->cfg.agg_select_policy = i; return 0; } static int lacp_load_config(struct teamd_context *ctx, struct lacp *lacp) { int err; int tmp; const char *agg_select_policy_name; err = teamd_config_bool_get(ctx, &lacp->cfg.active, "$.runner.active"); if (err) lacp->cfg.active = LACP_CFG_DFLT_ACTIVE; teamd_log_dbg(ctx, "Using active \"%d\".", lacp->cfg.active); err = teamd_config_int_get(ctx, &tmp, "$.runner.sys_prio"); if (err) { lacp->cfg.sys_prio = LACP_CFG_DFLT_SYS_PRIO; } else if (tmp < 0 || tmp > USHRT_MAX) { teamd_log_err("\"sys_prio\" value is out of its limits."); return -EINVAL; } else { lacp->cfg.sys_prio = tmp; } teamd_log_dbg(ctx, "Using sys_prio \"%d\".", lacp->cfg.sys_prio); err = teamd_config_bool_get(ctx, &lacp->cfg.fast_rate, "$.runner.fast_rate"); if (err) lacp->cfg.fast_rate = LACP_CFG_DFLT_FAST_RATE; teamd_log_dbg(ctx, "Using fast_rate \"%d\".", lacp->cfg.fast_rate); err = teamd_config_int_get(ctx, &tmp, "$.runner.min_ports"); if (err) { lacp->cfg.min_ports = LACP_CFG_DFLT_MIN_PORTS; } else if (tmp < 1 || tmp > LACP_CFG_DFLT_MIN_PORTS_MAX) { teamd_log_err("\"min_ports\" value is out of its limits."); return -EINVAL; } else { lacp->cfg.min_ports = tmp; } teamd_log_dbg(ctx, "Using min_ports \"%d\".", lacp->cfg.min_ports); err = teamd_config_string_get(ctx, &agg_select_policy_name, "$.runner.agg_select_policy"); if (err) agg_select_policy_name = NULL; err = lacp_assign_agg_select_policy(lacp, agg_select_policy_name); if (err) { teamd_log_err("Unknown \"agg_select_policy\" named \"%s\" passed.", agg_select_policy_name); return err; } teamd_log_dbg(ctx, "Using agg_select_policy \"%s\".", lacp_get_agg_select_policy_name(lacp)); return 0; } static bool lacp_port_loopback_free(struct lacp_port *lacp_port) { if (!memcmp(lacp_port->actor.system, lacp_port->partner.system, ETH_ALEN)) { teamd_log_warn("%s: Port seems to be loopbacked to the same " "team device.", lacp_port->tdport->ifname); return false; } return true; } static bool lacp_port_selectable_state(struct lacp_port *lacp_port) { if (lacp_port->state == PORT_STATE_CURRENT) return true; return false; } static bool lacp_port_unselectable_state(struct lacp_port *lacp_port) { if (lacp_port->state == PORT_STATE_CURRENT || lacp_port->state == PORT_STATE_EXPIRED) return false; return true; } static bool lacp_port_selected(struct lacp_port *lacp_port) { return lacp_port->agg_lead; } static int lacp_port_should_be_enabled(struct lacp_port *lacp_port) { struct lacp *lacp = lacp_port->lacp; if (lacp_port_selected(lacp_port) && lacp_port->agg_lead == lacp->selected_agg_lead && lacp_port->partner.state & INFO_STATE_SYNCHRONIZATION) return true; return false; } static int lacp_port_should_be_disabled(struct lacp_port *lacp_port) { struct lacp *lacp = lacp_port->lacp; if (!lacp_port_selected(lacp_port) || lacp_port->agg_lead != lacp->selected_agg_lead || !(lacp_port->partner.state & INFO_STATE_SYNCHRONIZATION)) return true; return false; } static int lacp_port_update_enabled(struct lacp_port *lacp_port) { return teamd_port_check_enable(lacp_port->ctx, lacp_port->tdport, lacp_port_should_be_enabled(lacp_port), lacp_port_should_be_disabled(lacp_port)); } static bool lacp_ports_aggregable(struct lacp_port *lacp_port1, struct lacp_port *lacp_port2) { if (lacp_port1->partner.key != lacp_port2->partner.key || lacp_port1->actor.key != lacp_port2->actor.key) return false; if (memcmp(lacp_port1->partner.system, lacp_port2->partner.system, ETH_ALEN)) return false; return true; } static bool lacp_port_correct_aggregation(struct lacp_port *checked_lacp_port) { struct teamd_port *tdport; struct lacp_port *lacp_port; /* Find first port in same aggregator and see if checked port is * aggregable with that. That's enough because all of the ports * in aggregator besides the checked one are aggregable with each other. */ teamd_for_each_tdport(tdport, checked_lacp_port->ctx) { lacp_port = lacp_port_get(checked_lacp_port->lacp, tdport); if (!lacp_port_selected(lacp_port) || lacp_port->agg_lead != checked_lacp_port->agg_lead || lacp_port == checked_lacp_port) continue; return lacp_ports_aggregable(lacp_port, checked_lacp_port); } return true; } static void get_lacp_port_prio_info(struct lacp_port *lacp_port, struct lacpdu_info *prio_info) { int prio_diff; int system_diff; prio_diff = ntohs(lacp_port->actor.system_priority) - ntohs(lacp_port->partner.system_priority); system_diff = memcmp(lacp_port->actor.system, lacp_port->partner.system, ETH_ALEN); if (prio_diff < 0 || (prio_diff == 0 && system_diff < 0)) *prio_info = lacp_port->actor; if (prio_diff > 0 || (prio_diff == 0 && system_diff >= 0)) *prio_info = lacp_port->partner; /* adjust values for further memcmp comparison */ prio_info->system_priority = ntohs(prio_info->system_priority); prio_info->key = 0; prio_info->port_priority = ntohs(prio_info->port_priority); prio_info->port = ntohs(prio_info->port); prio_info->state = 0; } static bool lacp_port_better_by_lacp_prio(struct lacp_port *lacp_port1, struct lacp_port *lacp_port2) { struct lacpdu_info prio_info1; struct lacpdu_info prio_info2; get_lacp_port_prio_info(lacp_port1, &prio_info1); get_lacp_port_prio_info(lacp_port2, &prio_info2); return memcmp(&prio_info1, &prio_info2, sizeof(prio_info1)) < 0; } static bool lacp_port_better_by_port_config(struct lacp_port *lacp_port1, struct lacp_port *lacp_port2) { int prio1 = teamd_port_prio(lacp_port1->ctx, lacp_port1->tdport); int prio2 = teamd_port_prio(lacp_port2->ctx, lacp_port2->tdport); return prio1 > prio2; } static bool lacp_port_better(struct lacp_port *lacp_port1, struct lacp_port *lacp_port2) { if (!lacp_port2) return true; switch (lacp_port1->lacp->cfg.agg_select_policy) { case LACP_AGG_SELECT_PORT_CONFIG: return lacp_port_better_by_port_config(lacp_port1, lacp_port2); default: return lacp_port_better_by_lacp_prio(lacp_port1, lacp_port2); } } static int lacp_set_carrier(struct lacp *lacp, bool carrier_up) { struct teamd_context *ctx = lacp->ctx; int err; if (lacp->carrier_up != carrier_up) { lacp->carrier_up = carrier_up; err = team_carrier_set(ctx->th, carrier_up); if (err) return err == -EOPNOTSUPP ? 0 : err; teamd_log_info("carrier changed to %s", carrier_up ? "UP" : "DOWN" ); } return 0; } static int lacp_update_carrier(struct lacp *lacp) { struct teamd_port *tdport; int ports_enabled; bool state; int err; ports_enabled = 0; teamd_for_each_tdport(tdport, lacp->ctx) { err = teamd_port_enabled(lacp->ctx, tdport, &state); if (err) return err; if (state && ++ports_enabled >= lacp->cfg.min_ports) return lacp_set_carrier(lacp, true); } return lacp_set_carrier(lacp, false); } static uint32_t lacp_get_agg_bandwidth(struct lacp_port *agg_lead) { struct teamd_port *tdport; struct lacp_port *lacp_port; uint32_t speed = 0; teamd_for_each_tdport(tdport, agg_lead->ctx) { lacp_port = lacp_port_get(agg_lead->lacp, tdport); if (lacp_port->agg_lead == agg_lead) speed += team_get_port_speed(tdport->team_port); } return speed; } static struct lacp_port *lacp_get_best_agg_by_bandwidth(struct lacp *lacp) { struct teamd_port *tdport; struct lacp_port *lacp_port; uint32_t speed; uint32_t best_speed = 0; struct lacp_port *best_agg_lead = NULL; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); if (!lacp_port_selected(lacp_port)) continue; speed = lacp_get_agg_bandwidth(lacp_port->agg_lead); if (speed > best_speed) { best_speed = speed; best_agg_lead = lacp_port->agg_lead; } } return best_agg_lead; } static unsigned int lacp_get_agg_port_count(struct lacp_port *agg_lead) { struct teamd_port *tdport; struct lacp_port *lacp_port; unsigned int port_count = 0; teamd_for_each_tdport(tdport, agg_lead->ctx) { lacp_port = lacp_port_get(agg_lead->lacp, tdport); if (lacp_port->agg_lead == agg_lead) port_count++; } return port_count; } static struct lacp_port *lacp_get_best_agg_by_port_count(struct lacp *lacp) { struct teamd_port *tdport; struct lacp_port *lacp_port; unsigned int port_count; unsigned int best_port_count = 0; struct lacp_port *best_agg_lead = NULL; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); if (!lacp_port_selected(lacp_port)) continue; port_count = lacp_get_agg_port_count(lacp_port->agg_lead); if (port_count > best_port_count) { best_port_count = port_count; best_agg_lead = lacp_port->agg_lead; } } return best_agg_lead; } static struct lacp_port *lacp_get_best_agg_by_best_port(struct lacp *lacp) { struct teamd_port *tdport; struct lacp_port *lacp_port; struct lacp_port *best_agg_lead = NULL; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); if (!lacp_port_selected(lacp_port)) continue; if (lacp_port_better(lacp_port->agg_lead, best_agg_lead)) best_agg_lead = lacp_port->agg_lead; } return best_agg_lead; } static bool lacp_agg_sticky(struct lacp_port *agg_lead) { struct teamd_port *tdport; struct lacp_port *lacp_port; teamd_for_each_tdport(tdport, agg_lead->ctx) { lacp_port = lacp_port_get(agg_lead->lacp, tdport); if (lacp_port->agg_lead == agg_lead && lacp_port->cfg.sticky) return true; } return false; } static struct lacp_port *lacp_get_next_agg(struct lacp *lacp) { struct lacp_port *next_agg_lead = lacp->selected_agg_lead; switch (lacp->cfg.agg_select_policy) { case LACP_AGG_SELECT_LACP_PRIO: next_agg_lead = lacp_get_best_agg_by_best_port(lacp); break; case LACP_AGG_SELECT_LACP_PRIO_STABLE: if (!lacp->selected_agg_lead) next_agg_lead = lacp_get_best_agg_by_best_port(lacp); break; case LACP_AGG_SELECT_BANDWIDTH: next_agg_lead = lacp_get_best_agg_by_bandwidth(lacp); break; case LACP_AGG_SELECT_COUNT: next_agg_lead = lacp_get_best_agg_by_port_count(lacp); break; case LACP_AGG_SELECT_PORT_CONFIG: if (!lacp->selected_agg_lead || !lacp_agg_sticky(lacp->selected_agg_lead)) next_agg_lead = lacp_get_best_agg_by_best_port(lacp); break; } return next_agg_lead; } static struct lacp_port *lacp_get_agg_lead(struct lacp_port *for_lacp_port) { struct lacp *lacp = for_lacp_port->lacp; struct teamd_port *tdport; struct lacp_port *lacp_port; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); if (!lacp_port_selected(lacp_port) || lacp_port == for_lacp_port) continue; if (lacp_ports_aggregable(lacp_port, for_lacp_port)) return lacp_port->agg_lead; } /* If no suitable aggregator found, the port is self-lead. */ return for_lacp_port; } static void lacp_switch_agg_lead(struct lacp_port *agg_lead, struct lacp_port *new_agg_lead) { struct lacp *lacp = agg_lead->lacp; struct teamd_port *tdport; struct lacp_port *lacp_port; teamd_log_dbg(agg_lead->ctx, "Renaming aggregator %u to %u", lacp_agg_id(agg_lead), lacp_agg_id(new_agg_lead)); if (lacp->selected_agg_lead == agg_lead) lacp->selected_agg_lead = new_agg_lead; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); if (lacp_port->agg_lead == agg_lead) lacp_port->agg_lead = new_agg_lead; } } static void lacp_port_agg_select(struct lacp_port *lacp_port) { struct lacp_port *agg_lead; teamd_log_dbg(lacp_port->ctx, "%s: Selecting LACP port", lacp_port->tdport->ifname); agg_lead = lacp_get_agg_lead(lacp_port); lacp_port->agg_lead = agg_lead; if (lacp_port_better(lacp_port, agg_lead)) lacp_switch_agg_lead(agg_lead, lacp_port); teamd_log_dbg(lacp_port->ctx, "%s: LACP port selected into aggregator %u", lacp_port->tdport->ifname, lacp_port_agg_id(lacp_port)); } static struct lacp_port *lacp_find_new_agg_lead(struct lacp_port *old_agg_lead) { struct teamd_port *tdport; struct lacp_port *lacp_port; struct lacp_port *new_agg_lead = NULL; teamd_for_each_tdport(tdport, old_agg_lead->ctx) { lacp_port = lacp_port_get(old_agg_lead->lacp, tdport); if (lacp_port->agg_lead == old_agg_lead && lacp_port_better(lacp_port, new_agg_lead)) new_agg_lead = lacp_port; } return new_agg_lead; } static void lacp_port_agg_unselect(struct lacp_port *lacp_port) { struct lacp_port *agg_lead = lacp_port->agg_lead; teamd_log_dbg(lacp_port->ctx, "%s: Unselecting LACP port", lacp_port->tdport->ifname); teamd_log_dbg(lacp_port->ctx, "%s: LACP port unselected from aggregator %u", lacp_port->tdport->ifname, lacp_port_agg_id(lacp_port)); lacp_port->agg_lead = NULL; if (lacp_port == agg_lead) { /* In case currently unselected port is aggregator lead lead, * find new one. */ struct lacp_port *new_agg_lead; new_agg_lead = lacp_find_new_agg_lead(agg_lead); lacp_switch_agg_lead(agg_lead, new_agg_lead); } } static int lacp_ports_update_enabled(struct lacp *lacp) { struct teamd_port *tdport; struct lacp_port *lacp_port; int err; teamd_for_each_tdport(tdport, lacp->ctx) { lacp_port = lacp_port_get(lacp, tdport); err = lacp_port_update_enabled(lacp_port); if (err) return err; } return 0; } static int lacp_selected_agg_update(struct lacp *lacp, struct lacp_port *next_agg_lead) { int err; if (!next_agg_lead) next_agg_lead = lacp_get_next_agg(lacp); if (lacp->selected_agg_lead != next_agg_lead) teamd_log_dbg(lacp->ctx, "Selecting aggregator %u", lacp_agg_id(next_agg_lead)); lacp->selected_agg_lead = next_agg_lead; err = lacp_ports_update_enabled(lacp); if (err) return err; err = lacp_update_carrier(lacp); if (err) return err; return 0; } static bool lacp_port_mergeable(struct lacp_port *lacp_port) { /* Port can be merged with other aggregator only in case it is * alone in aggragator and is aggregable with some other port. */ return lacp_port_is_agg_lead(lacp_port) && lacp_get_agg_port_count(lacp_port) == 1 && lacp_get_agg_lead(lacp_port) != lacp_port; } static int lacp_port_agg_update(struct lacp_port *lacp_port) { if (lacp_port_selected(lacp_port) && (lacp_port_unselectable_state(lacp_port) || !lacp_port_loopback_free(lacp_port) || !lacp_port_correct_aggregation(lacp_port) || lacp_port_mergeable(lacp_port))) lacp_port_agg_unselect(lacp_port); if (!lacp_port_selected(lacp_port) && (lacp_port_selectable_state(lacp_port) && lacp_port_loopback_free(lacp_port))) lacp_port_agg_select(lacp_port); return lacp_selected_agg_update(lacp_port->lacp, NULL); } static const char slow_addr[ETH_ALEN] = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x02 }; static int __slow_addr_add_del(struct lacp_port *lacp_port, bool add) { struct ifreq ifr; struct sockaddr *sa; char *devname = lacp_port->tdport->ifname; int ret; memset(&ifr, 0, sizeof(struct ifreq)); sa = (struct sockaddr *) &ifr.ifr_addr; sa->sa_family = AF_UNSPEC; memcpy(sa->sa_data, slow_addr, sizeof(slow_addr)); memcpy(ifr.ifr_name, devname, strlen(devname)); ret = ioctl(lacp_port->sock, add ? SIOCADDMULTI : SIOCDELMULTI, &ifr); if (ret == -1) { teamd_log_err("ioctl %s failed.", add ? "SIOCADDMULTI" : "SIOCDELMULTI"); return -errno; } return 0; } static int slow_addr_add(struct lacp_port *lacp_port) { return __slow_addr_add_del(lacp_port, true); } static int slow_addr_del(struct lacp_port *lacp_port) { return __slow_addr_add_del(lacp_port, false); } /* Values are in ms */ #define LACP_PERIODIC_SHORT 1000 #define LACP_PERIODIC_LONG 30000 /* time = periodic_interval * LACP_PERIODIC_MUL */ #define LACP_PERIODIC_MUL 3 #define LACP_SOCKET_CB_NAME "lacp_socket" #define LACP_PERIODIC_CB_NAME "lacp_periodic" #define LACP_TIMEOUT_CB_NAME "lacp_timeout" static int lacp_port_timeout_set(struct lacp_port *lacp_port, bool fast_forced) { int err; struct timespec ts; int ms; ms = fast_forced || lacp_port->lacp->cfg.fast_rate ? LACP_PERIODIC_SHORT: LACP_PERIODIC_LONG; ms *= LACP_PERIODIC_MUL; ms_to_timespec(&ts, ms); err = teamd_loop_callback_timer_set(lacp_port->ctx, LACP_TIMEOUT_CB_NAME, lacp_port, NULL, &ts); if (err) { teamd_log_err("Failed to set timeout timer."); return err; } return 0; } static int lacp_port_periodic_set(struct lacp_port *lacp_port) { int err; struct timespec ts; int ms; int fast_on; fast_on = lacp_port->partner.state & INFO_STATE_LACP_TIMEOUT; teamd_log_dbg(lacp_port->ctx, "%s: Setting periodic timer to \"%s\".", lacp_port->tdport->ifname, fast_on ? "fast": "slow"); ms = fast_on ? LACP_PERIODIC_SHORT: LACP_PERIODIC_LONG; ms_to_timespec(&ts, ms); err = teamd_loop_callback_timer_set(lacp_port->ctx, LACP_PERIODIC_CB_NAME, lacp_port, &ts, NULL); if (err) { teamd_log_err("Failed to set periodic timer."); return err; } return 0; } static bool lacp_port_should_be_active(struct lacp_port *lacp_port) { return (lacp_port->partner.state & INFO_STATE_LACP_ACTIVITY) || (lacp_port->lacp->cfg.active); } static void lacp_port_periodic_cb_change_enabled(struct lacp_port *lacp_port) { if (lacp_port_should_be_active(lacp_port) && lacp_port->periodic_on) teamd_loop_callback_enable(lacp_port->ctx, LACP_PERIODIC_CB_NAME, lacp_port); else teamd_loop_callback_disable(lacp_port->ctx, LACP_PERIODIC_CB_NAME, lacp_port); } static void lacp_port_periodic_on(struct lacp_port *lacp_port) { lacp_port->periodic_on = true; lacp_port_periodic_cb_change_enabled(lacp_port); } static void lacp_port_periodic_off(struct lacp_port *lacp_port) { lacp_port->periodic_on = false; lacp_port_periodic_cb_change_enabled(lacp_port); } static int lacp_port_partner_update(struct lacp_port *lacp_port) { uint8_t state_changed; int err; state_changed = lacp_port->partner.state ^ lacp_port->__partner_last.state; if (state_changed & INFO_STATE_LACP_TIMEOUT) { err = lacp_port_periodic_set(lacp_port); if (err) return err; } if (state_changed & INFO_STATE_LACP_ACTIVITY) lacp_port_periodic_cb_change_enabled(lacp_port); lacp_port->__partner_last = lacp_port->partner; return 0; } static void lacp_port_actor_system_update(struct lacp_port *lacp_port) { struct lacpdu_info *actor = &lacp_port->actor; memcpy(actor->system, lacp_port->ctx->hwaddr, ETH_ALEN); } static void lacp_port_actor_init(struct lacp_port *lacp_port) { struct lacpdu_info *actor = &lacp_port->actor; actor->system_priority = htons(lacp_port->lacp->cfg.sys_prio); actor->key = htons(lacp_port->cfg.lacp_key); actor->port_priority = htons(lacp_port->cfg.lacp_prio); actor->port = htons(lacp_port->tdport->ifindex); lacp_port_actor_system_update(lacp_port); } static void lacp_port_actor_update(struct lacp_port *lacp_port) { uint8_t state = 0; if (lacp_port->lacp->cfg.active) state |= INFO_STATE_LACP_ACTIVITY; if (lacp_port->lacp->cfg.fast_rate) state |= INFO_STATE_LACP_TIMEOUT; if (lacp_port_selected(lacp_port) && lacp_port_agg_selected(lacp_port)) { state |= INFO_STATE_SYNCHRONIZATION; state &= ~(INFO_STATE_COLLECTING | INFO_STATE_DISTRIBUTING); if (lacp_port->partner.state & INFO_STATE_SYNCHRONIZATION) state |= INFO_STATE_COLLECTING | INFO_STATE_DISTRIBUTING; } if (lacp_port->state == PORT_STATE_EXPIRED) state |= INFO_STATE_EXPIRED; if (lacp_port->state == PORT_STATE_DEFAULTED) state |= INFO_STATE_DEFAULTED; if (teamd_port_count(lacp_port->ctx) > 0) state |= INFO_STATE_AGGREGATION; teamd_log_dbg(lacp_port->ctx, "%s: lacp info state: 0x%02X.", lacp_port->tdport->ifname, state); lacp_port->actor.state = state; } static int lacpdu_send(struct lacp_port *lacp_port); static int lacp_port_set_state(struct lacp_port *lacp_port, enum lacp_port_state new_state) { bool admin_state = team_get_ifinfo_admin_state(lacp_port->ctx->ifinfo); int err; if (new_state == lacp_port->state || (!admin_state && new_state != PORT_STATE_DISABLED)) return 0; if (new_state == PORT_STATE_DISABLED) lacp_port_periodic_off(lacp_port); else lacp_port_periodic_on(lacp_port); switch(new_state) { case PORT_STATE_CURRENT: break; case PORT_STATE_EXPIRED: teamd_loop_callback_enable(lacp_port->ctx, LACP_PERIODIC_CB_NAME, lacp_port); /* * This is a transient state; the LACP_Timeout settings allow * the Actor to transmit LACPDUs rapidly in an attempt to * re-establish communication with the Partner. */ lacp_port->partner.state |= INFO_STATE_LACP_TIMEOUT; lacp_port->partner.state &= ~INFO_STATE_SYNCHRONIZATION; err = lacp_port_partner_update(lacp_port); if (err) return err; lacp_port_timeout_set(lacp_port, true); teamd_loop_callback_enable(lacp_port->ctx, LACP_TIMEOUT_CB_NAME, lacp_port); break; case PORT_STATE_DEFAULTED: teamd_loop_callback_disable(lacp_port->ctx, LACP_TIMEOUT_CB_NAME, lacp_port); memset(&lacp_port->partner, 0, sizeof(lacp_port->partner)); lacp_port->partner.state |= INFO_STATE_LACP_TIMEOUT; err = lacp_port_partner_update(lacp_port); if (err) return err; break; case PORT_STATE_DISABLED: memset(&lacp_port->partner, 0, sizeof(lacp_port->partner)); err = lacp_port_partner_update(lacp_port); if (err) return err; break; } teamd_log_info("%s: Changed port state: \"%s\" -> \"%s\"", lacp_port->tdport->ifname, lacp_port_state_name[lacp_port->state], lacp_port_state_name[new_state]); lacp_port->state = new_state; err = lacp_port_agg_update(lacp_port); if (err) return err; lacp_port_actor_update(lacp_port); return lacpdu_send(lacp_port); } static enum lacp_port_state lacp_port_get_state(struct lacp_port *lacp_port) { return lacp_port->state; } /* Called when ethtool port link state is changed and when port is added*/ static int lacp_port_link_update(struct lacp_port *lacp_port) { struct team_port *team_port = lacp_port->tdport->team_port; bool linkup = team_is_port_link_up(team_port); uint32_t speed = team_get_port_speed(team_port); uint8_t duplex = team_get_port_duplex(team_port); int err; if (linkup != lacp_port->__link_last.up || duplex != lacp_port->__link_last.duplex) { /* If duplex is 0, meaning half-duplex, it should be set * to disabled state. However some drivers, like virtio_net * does not report speed and duplex. In that case, kernel * will provide speed == 0 and duplex == 0. If that is the * case now, do not set disabled state and allow such devices * to work properly. */ if (linkup && (!duplex == !speed)) err = lacp_port_set_state(lacp_port, PORT_STATE_EXPIRED); else err = lacp_port_set_state(lacp_port, PORT_STATE_DISABLED); if (err) return err; } lacp_port->__link_last.up = linkup; lacp_port->__link_last.speed = speed; lacp_port->__link_last.duplex = duplex; return 0; } static int lacpdu_send(struct lacp_port *lacp_port) { struct lacpdu lacpdu; struct sockaddr_ll ll_my; struct sockaddr_ll ll_slow; char *hwaddr; unsigned char hwaddr_len; int err; bool admin_state; admin_state = team_get_ifinfo_admin_state(lacp_port->ctx->ifinfo); if (!admin_state) return 0; err = teamd_getsockname_hwaddr(lacp_port->sock, &ll_my, 0); if (err) return err; ll_slow = ll_my; memcpy(ll_slow.sll_addr, slow_addr, ll_slow.sll_halen); memcpy(lacp_port->actor.system, lacp_port->ctx->hwaddr, ETH_ALEN); hwaddr = team_get_ifinfo_orig_hwaddr(lacp_port->tdport->team_ifinfo); hwaddr_len = team_get_ifinfo_orig_hwaddr_len(lacp_port->tdport->team_ifinfo); if (hwaddr_len != ETH_ALEN) return 0; lacpdu_init(&lacpdu); lacpdu.actor = lacp_port->actor; lacpdu.partner = lacp_port->partner; memcpy(lacpdu.hdr.ether_shost, hwaddr, hwaddr_len); memcpy(lacpdu.hdr.ether_dhost, ll_slow.sll_addr, ll_slow.sll_halen); lacpdu.hdr.ether_type = htons(ETH_P_SLOW); err = teamd_send(lacp_port->sock, &lacpdu, sizeof(lacpdu), 0); return err; } static int lacpdu_recv(struct lacp_port *lacp_port) { struct lacpdu lacpdu; struct sockaddr_ll ll_from; int err; err = teamd_recvfrom(lacp_port->sock, &lacpdu, sizeof(lacpdu), 0, (struct sockaddr *) &ll_from, sizeof(ll_from)); if (err <= 0) return err; if (!teamd_port_present(lacp_port->ctx, lacp_port->tdport)) return 0; if (!lacpdu_check(&lacpdu)) return 0; /* Check if we have correct info about the other side */ if (memcmp(&lacpdu.actor, &lacp_port->partner, sizeof(struct lacpdu_info))) { lacp_port->partner = lacpdu.actor; err = lacp_port_partner_update(lacp_port); if (err) return err; err = lacp_port_agg_update(lacp_port); if (err) return err; } err = lacp_port_set_state(lacp_port, PORT_STATE_CURRENT); if (err) return err; lacp_port_actor_update(lacp_port); /* Check if the other side has correct info about us */ if (memcmp(&lacpdu.partner, &lacp_port->actor, sizeof(struct lacpdu_info))) { err = lacpdu_send(lacp_port); if (err) return err; } err = lacp_port_timeout_set(lacp_port, false); if (err) { return err; } teamd_loop_callback_enable(lacp_port->ctx, LACP_TIMEOUT_CB_NAME, lacp_port); return 0; } static int lacp_callback_timeout(struct teamd_context *ctx, int events, void *priv) { struct lacp_port *lacp_port = priv; int err = 0; switch (lacp_port_get_state(lacp_port)) { case PORT_STATE_CURRENT: err = lacp_port_set_state(lacp_port, PORT_STATE_EXPIRED); break; case PORT_STATE_EXPIRED: err = lacp_port_set_state(lacp_port, PORT_STATE_DEFAULTED); break; case PORT_STATE_DEFAULTED: case PORT_STATE_DISABLED: /* This can't happen */ break; } return err; } static int lacp_callback_periodic(struct teamd_context *ctx, int events, void *priv) { struct lacp_port *lacp_port = priv; lacp_port_actor_update(lacp_port); return lacpdu_send(lacp_port); } static int lacp_callback_socket(struct teamd_context *ctx, int events, void *priv) { struct lacp_port *lacp_port = priv; return lacpdu_recv(lacp_port); } static int lacp_port_set_mac(struct teamd_context *ctx, struct teamd_port *tdport) { int err; err = team_hwaddr_set(ctx->th, tdport->ifindex, ctx->hwaddr, ctx->hwaddr_len); if (err) { teamd_log_err("%s: Failed to set hardware address. ", tdport->ifname); return err; } return 0; } static int lacp_port_load_config(struct teamd_context *ctx, struct lacp_port *lacp_port) { const char *port_name = lacp_port->tdport->ifname; int err; int tmp; err = teamd_config_int_get(ctx, &tmp, "$.ports.%s.lacp_prio", port_name); if (err) { lacp_port->cfg.lacp_prio = LACP_PORT_CFG_DFLT_LACP_PRIO; } else if (tmp < 0 || tmp > USHRT_MAX) { teamd_log_err("%s: \"lacp_prio\" value is out of its limits.", port_name); return -EINVAL; } else { lacp_port->cfg.lacp_prio = tmp; } teamd_log_dbg(ctx, "%s: Using lacp_prio \"%d\".", port_name, lacp_port->cfg.lacp_prio); err = teamd_config_int_get(ctx, &tmp, "$.ports.%s.lacp_key", port_name); if (err) { lacp_port->cfg.lacp_key = LACP_PORT_CFG_DFLT_LACP_KEY; } else if (tmp < 0 || tmp > USHRT_MAX) { teamd_log_err("%s: \"lacp_key\" value is out of its limits.", port_name); return -EINVAL; } else { lacp_port->cfg.lacp_key = tmp; } teamd_log_dbg(ctx, "%s: Using lacp_key \"%d\".", port_name, lacp_port->cfg.lacp_key); err = teamd_config_bool_get(ctx, &lacp_port->cfg.sticky, "$.ports.%s.sticky", port_name); if (err) lacp_port->cfg.sticky = LACP_PORT_CFG_DFLT_STICKY; teamd_log_dbg(ctx, "%s: Using sticky \"%d\".", port_name, lacp_port->cfg.sticky); return 0; } static int lacp_port_added(struct teamd_context *ctx, struct teamd_port *tdport, void *priv, void *creator_priv) { struct lacp_port *lacp_port = priv; struct lacp *lacp = creator_priv; int err; lacp_port->ctx = ctx; lacp_port->tdport = tdport; lacp_port->lacp = lacp; err = lacp_port_load_config(ctx, lacp_port); if (err) { teamd_log_err("Failed to load port config."); return err; } err = teamd_packet_sock_open_type(SOCK_RAW, &lacp_port->sock, tdport->ifindex, htons(ETH_P_SLOW), NULL, NULL); if (err) return err; err = slow_addr_add(lacp_port); if (err) goto close_sock; err = teamd_loop_callback_fd_add(ctx, LACP_SOCKET_CB_NAME, lacp_port, lacp_callback_socket, lacp_port->sock, TEAMD_LOOP_FD_EVENT_READ); if (err) { teamd_log_err("Failed add socket callback."); goto slow_addr_del; } err = teamd_loop_callback_timer_add(ctx, LACP_PERIODIC_CB_NAME, lacp_port, lacp_callback_periodic); if (err) { teamd_log_err("Failed add periodic callback timer"); goto socket_callback_del; } err = lacp_port_periodic_set(lacp_port); if (err) goto periodic_callback_del; err = teamd_loop_callback_timer_add(ctx, LACP_TIMEOUT_CB_NAME, lacp_port, lacp_callback_timeout); if (err) { teamd_log_err("Failed add timeout callback timer"); goto periodic_callback_del; } /* Newly added ports are disabled */ err = team_set_port_enabled(ctx->th, tdport->ifindex, false); if (err) { teamd_log_err("%s: Failed to disable port.", tdport->ifname); if (!TEAMD_ENOENT(err)) goto timeout_callback_del; } err = lacp_port_set_mac(ctx, tdport); if (err) goto timeout_callback_del; lacp_port_actor_init(lacp_port); lacp_port_link_update(lacp_port); teamd_loop_callback_enable(ctx, LACP_SOCKET_CB_NAME, lacp_port); return 0; timeout_callback_del: teamd_loop_callback_del(ctx, LACP_TIMEOUT_CB_NAME, lacp_port); periodic_callback_del: teamd_loop_callback_del(ctx, LACP_PERIODIC_CB_NAME, lacp_port); socket_callback_del: teamd_loop_callback_del(ctx, LACP_SOCKET_CB_NAME, lacp_port); slow_addr_del: slow_addr_del(lacp_port); close_sock: close(lacp_port->sock); return err; } static void lacp_port_removed(struct teamd_context *ctx, struct teamd_port *tdport, void *priv, void *creator_priv) { struct lacp_port *lacp_port = priv; lacp_port_set_state(lacp_port, PORT_STATE_DISABLED); teamd_loop_callback_del(ctx, LACP_TIMEOUT_CB_NAME, lacp_port); teamd_loop_callback_del(ctx, LACP_PERIODIC_CB_NAME, lacp_port); teamd_loop_callback_del(ctx, LACP_SOCKET_CB_NAME, lacp_port); slow_addr_del(lacp_port); close(lacp_port->sock); } static const struct teamd_port_priv lacp_port_priv = { .init = lacp_port_added, .fini = lacp_port_removed, .priv_size = sizeof(struct lacp_port), }; static int lacp_event_watch_hwaddr_changed(struct teamd_context *ctx, void *priv) { struct lacp *lacp = priv; struct teamd_port *tdport; int err; teamd_for_each_tdport(tdport, ctx) { struct lacp_port *lacp_port = lacp_port_get(lacp, tdport); err = lacp_port_set_mac(ctx, tdport); if (err) return err; lacp_port_actor_system_update(lacp_port); } return 0; } static int lacp_event_watch_port_hwaddr_changed(struct teamd_context *ctx, struct teamd_port *tdport, void *priv) { struct lacp_port *lacp_port; struct lacp *lacp = priv; int err; if (!teamd_port_present(ctx, tdport)) return 0; if (!memcmp(team_get_ifinfo_hwaddr(tdport->team_ifinfo), ctx->hwaddr, ctx->hwaddr_len)) return 0; err = lacp_port_set_mac(ctx, tdport); if (err) return err; lacp_port = lacp_port_get(lacp, tdport); lacp_port_actor_system_update(lacp_port); return 0; } static int lacp_event_watch_admin_state_changed(struct teamd_context *ctx, void *priv) { struct lacp *lacp = priv; struct teamd_port *tdport; bool admin_state; int err; admin_state = team_get_ifinfo_admin_state(ctx->ifinfo); teamd_for_each_tdport(tdport, ctx) { struct lacp_port *lacp_port = lacp_port_get(lacp, tdport); err = lacp_port_set_state(lacp_port, admin_state?PORT_STATE_EXPIRED:PORT_STATE_DISABLED); if (err) return err; } return 0; } static int lacp_event_watch_port_added(struct teamd_context *ctx, struct teamd_port *tdport, void *priv) { struct lacp *lacp = priv; int err; err = teamd_port_priv_create(tdport, &lacp_port_priv, lacp); if (err) return err; return teamd_balancer_port_added(lacp->tb, tdport); } static void lacp_event_watch_port_removing(struct teamd_context *ctx, struct teamd_port *tdport, void *priv) { struct lacp *lacp = priv; struct lacp_port *lacp_port = lacp_port_get(lacp, tdport); /* Ensure that no incoming LACPDU is going to be processed. */ teamd_loop_callback_disable(ctx, LACP_SOCKET_CB_NAME, lacp_port); lacp_port_set_state(lacp_port, PORT_STATE_DISABLED); } static void lacp_event_watch_port_removed(struct teamd_context *ctx, struct teamd_port *tdport, void *priv) { struct lacp *lacp = priv; teamd_balancer_port_removed(lacp->tb, tdport); } static int lacp_event_watch_port_changed(struct teamd_context *ctx, struct teamd_port *tdport, void *priv) { struct lacp *lacp = priv; struct lacp_port *lacp_port = lacp_port_get(lacp, tdport); return lacp_port_link_update(lacp_port); } static const struct teamd_event_watch_ops lacp_event_watch_ops = { .hwaddr_changed = lacp_event_watch_hwaddr_changed, .port_hwaddr_changed = lacp_event_watch_port_hwaddr_changed, .port_added = lacp_event_watch_port_added, .port_removing = lacp_event_watch_port_removing, .port_removed = lacp_event_watch_port_removed, .port_changed = lacp_event_watch_port_changed, .admin_state_changed = lacp_event_watch_admin_state_changed, }; static int lacp_carrier_init(struct teamd_context *ctx, struct lacp *lacp) { int err; /* initialize carrier control */ err = team_carrier_set(ctx->th, false); if (err && err != -EOPNOTSUPP) { teamd_log_err("Failed to set carrier down."); return err; } lacp->carrier_up = false; return 0; } static int lacp_carrier_fini(struct teamd_context *ctx, struct lacp *lacp) { int err; err = team_carrier_set(ctx->th, false); if (err && err != -EOPNOTSUPP) { teamd_log_err("Failed to set carrier down."); return err; } lacp->carrier_up = false; return 0; } static int lacp_state_active_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { struct lacp *lacp = priv; gsc->data.bool_val = lacp->cfg.active; return 0; } static int lacp_state_sys_prio_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { struct lacp *lacp = priv; gsc->data.int_val = lacp->cfg.sys_prio; return 0; } static int lacp_state_fast_rate_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { struct lacp *lacp = priv; gsc->data.bool_val = lacp->cfg.fast_rate; return 0; } static int lacp_state_select_policy_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { struct lacp *lacp = priv; gsc->data.str_val.ptr = lacp_get_agg_select_policy_name(lacp); return 0; } static const struct teamd_state_val lacp_state_vals[] = { { .subpath = "active", .type = TEAMD_STATE_ITEM_TYPE_BOOL, .getter = lacp_state_active_get, }, { .subpath = "sys_prio", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_state_sys_prio_get, }, { .subpath = "fast_rate", .type = TEAMD_STATE_ITEM_TYPE_BOOL, .getter = lacp_state_fast_rate_get, }, { .subpath = "select_policy", .type = TEAMD_STATE_ITEM_TYPE_STRING, .getter = lacp_state_select_policy_get, }, }; static struct lacp_port *lacp_port_gsc(struct team_state_gsc *gsc, void *priv) { struct lacp *lacp = priv; return lacp_port_get(lacp, gsc->info.tdport); } static struct lacpdu_info *lacp_port_actor_gsc(struct team_state_gsc *gsc, void *priv) { return &lacp_port_gsc(gsc, priv)->actor; } static int lacp_port_actor_state_system_priority_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_actor_gsc(gsc, priv)->system_priority); return 0; } static int lacp_port_actor_state_system_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { char *addr = (char *) lacp_port_actor_gsc(gsc, priv)->system; char *addr_str; addr_str = a_hwaddr_str(addr, ETH_ALEN); if (!addr_str) return -ENOMEM; gsc->data.str_val.ptr = addr_str; gsc->data.str_val.free = true; return 0; } static int lacp_port_actor_state_key_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_actor_gsc(gsc, priv)->key); return 0; } static int lacp_port_actor_state_port_priority_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_actor_gsc(gsc, priv)->port_priority); return 0; } static int lacp_port_actor_state_port_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_actor_gsc(gsc, priv)->port); return 0; } static int lacp_port_actor_state_state_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = lacp_port_actor_gsc(gsc, priv)->state; return 0; } static const struct teamd_state_val lacp_port_actor_state_vals[] = { { .subpath = "system_priority", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_actor_state_system_priority_get, }, { .subpath = "system", .type = TEAMD_STATE_ITEM_TYPE_STRING, .getter = lacp_port_actor_state_system_get, }, { .subpath = "key", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_actor_state_key_get, }, { .subpath = "port_priority", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_actor_state_port_priority_get, }, { .subpath = "port", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_actor_state_port_get, }, { .subpath = "state", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_actor_state_state_get, }, }; static struct lacpdu_info *lacp_port_partner_gsc(struct team_state_gsc *gsc, void *priv) { return &lacp_port_gsc(gsc, priv)->partner; } static int lacp_port_partner_state_system_priority_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_partner_gsc(gsc, priv)->system_priority); return 0; } static int lacp_port_partner_state_system_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { char *addr = (char *) lacp_port_partner_gsc(gsc, priv)->system; char *addr_str; addr_str = a_hwaddr_str(addr, ETH_ALEN); if (!addr_str) return -ENOMEM; gsc->data.str_val.ptr = addr_str; gsc->data.str_val.free = true; return 0; } static int lacp_port_partner_state_key_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_partner_gsc(gsc, priv)->key); return 0; } static int lacp_port_partner_state_port_priority_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_partner_gsc(gsc, priv)->port_priority); return 0; } static int lacp_port_partner_state_port_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = ntohs(lacp_port_partner_gsc(gsc, priv)->port); return 0; } static int lacp_port_partner_state_state_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = lacp_port_partner_gsc(gsc, priv)->state; return 0; } static const struct teamd_state_val lacp_port_partner_state_vals[] = { { .subpath = "system_priority", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_partner_state_system_priority_get, }, { .subpath = "system", .type = TEAMD_STATE_ITEM_TYPE_STRING, .getter = lacp_port_partner_state_system_get, }, { .subpath = "key", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_partner_state_key_get, }, { .subpath = "port_priority", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_partner_state_port_priority_get, }, { .subpath = "port", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_partner_state_port_get, }, { .subpath = "state", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_partner_state_state_get, }, }; static int lacp_port_state_selected_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.bool_val = lacp_port_selected(lacp_port_gsc(gsc, priv)); return 0; } static int lacp_port_state_aggregator_id_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = lacp_port_agg_id(lacp_port_gsc(gsc, priv)); return 0; } static int lacp_port_state_aggregator_selected_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.bool_val = lacp_port_agg_selected(lacp_port_gsc(gsc, priv)); return 0; } struct lacp_port_selected_set_info { struct teamd_workq workq; struct lacp *lacp; uint32_t ifindex; }; static int lacp_port_aggregator_select_work(struct teamd_context *ctx, struct teamd_workq *workq) { struct lacp_port_selected_set_info *info; struct teamd_port *tdport; struct lacp *lacp; struct lacp_port *lacp_port; info = get_container(workq, struct lacp_port_selected_set_info, workq); lacp = info->lacp; tdport = teamd_get_port(ctx, info->ifindex); free(info); if (!tdport) /* Port disapeared in between, ignore */ return 0; lacp_port = lacp_port_get(lacp, tdport); if (!lacp_port_selected(lacp_port)) return 0; return lacp_selected_agg_update(lacp_port->lacp, lacp_port->agg_lead); } static int lacp_port_state_aggregator_selected_set(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { struct lacp_port_selected_set_info *info; struct lacp *lacp = priv; if (!gsc->data.bool_val) return -EOPNOTSUPP; if (!lacp_port_selected(lacp_port_gsc(gsc, priv))) return -EINVAL; info = malloc(sizeof(*info)); if (!info) return -ENOMEM; teamd_workq_init_work(&info->workq, lacp_port_aggregator_select_work); info->lacp = lacp; info->ifindex = gsc->info.tdport->ifindex; teamd_workq_schedule_work(ctx, &info->workq); return 0; } static int lacp_port_state_state_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.str_val.ptr = lacp_port_state_name[lacp_port_gsc(gsc, priv)->state]; return 0; } static int lacp_port_state_key_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = lacp_port_gsc(gsc, priv)->cfg.lacp_key; return 0; } static int lacp_port_state_prio_get(struct teamd_context *ctx, struct team_state_gsc *gsc, void *priv) { gsc->data.int_val = lacp_port_gsc(gsc, priv)->cfg.lacp_prio; return 0; } static const struct teamd_state_val lacp_port_state_vals[] = { { .subpath = "selected", .type = TEAMD_STATE_ITEM_TYPE_BOOL, .getter = lacp_port_state_selected_get, }, { .subpath = "aggregator.id", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_state_aggregator_id_get, }, { .subpath = "aggregator.selected", .type = TEAMD_STATE_ITEM_TYPE_BOOL, .getter = lacp_port_state_aggregator_selected_get, .setter = lacp_port_state_aggregator_selected_set, }, { .subpath = "state", .type = TEAMD_STATE_ITEM_TYPE_STRING, .getter = lacp_port_state_state_get, }, { .subpath = "key", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_state_key_get, }, { .subpath = "prio", .type = TEAMD_STATE_ITEM_TYPE_INT, .getter = lacp_port_state_prio_get, }, { .subpath = "actor_lacpdu_info", .vals = lacp_port_actor_state_vals, .vals_count = ARRAY_SIZE(lacp_port_actor_state_vals), }, { .subpath = "partner_lacpdu_info", .vals = lacp_port_partner_state_vals, .vals_count = ARRAY_SIZE(lacp_port_partner_state_vals), }, }; static const struct teamd_state_val lacp_state_vgs[] = { { .subpath = "runner", .vals = lacp_state_vals, .vals_count = ARRAY_SIZE(lacp_state_vals), }, { .subpath = "runner", .vals = lacp_port_state_vals, .vals_count = ARRAY_SIZE(lacp_port_state_vals), .per_port = true, }, }; static const struct teamd_state_val lacp_state_vg = { .vals = lacp_state_vgs, .vals_count = ARRAY_SIZE(lacp_state_vgs), }; static int lacp_init(struct teamd_context *ctx, void *priv) { struct lacp *lacp = priv; int err; if (ctx->hwaddr_len != ETH_ALEN) { teamd_log_err("Unsupported device type."); return -EINVAL; } lacp->ctx = ctx; err = teamd_hash_func_set(ctx); if (err) return err; err = lacp_load_config(ctx, lacp); if (err) { teamd_log_err("Failed to load config values."); return err; } err = lacp_carrier_init(ctx, lacp); if (err) { teamd_log_err("Failed to initialize carrier."); return err; } err = teamd_event_watch_register(ctx, &lacp_event_watch_ops, lacp); if (err) { teamd_log_err("Failed to register event watch."); return err; } err = teamd_balancer_init(ctx, &lacp->tb); if (err) { teamd_log_err("Failed to init balanced."); goto event_watch_unregister; } err = teamd_state_val_register(ctx, &lacp_state_vg, lacp); if (err) { teamd_log_err("Failed to register state groups."); goto balancer_fini; } return 0; balancer_fini: teamd_balancer_fini(lacp->tb); event_watch_unregister: teamd_event_watch_unregister(ctx, &lacp_event_watch_ops, lacp); return err; } static void lacp_fini(struct teamd_context *ctx, void *priv) { struct lacp *lacp = priv; teamd_state_val_unregister(ctx, &lacp_state_vg, lacp); teamd_balancer_fini(lacp->tb); teamd_event_watch_unregister(ctx, &lacp_event_watch_ops, lacp); lacp_carrier_fini(ctx, lacp); } const struct teamd_runner teamd_runner_lacp = { .name = "lacp", .team_mode_name = "loadbalance", .priv_size = sizeof(struct lacp), .init = lacp_init, .fini = lacp_fini, };