|
| 1 | +From 95f2377a851b6ab49ab8042b239b67dfa20ec9d8 Mon Sep 17 00:00:00 2001 |
| 2 | +From: Lukas Stockner < [email protected]> |
| 3 | +Date: Fri, 13 May 2022 02:32:37 +0200 |
| 4 | +Subject: [PATCH] Extend LACP fallback support to multiple ports |
| 5 | + |
| 6 | +--- |
| 7 | + teamd/teamd_runner_lacp.c | 127 ++++++++++++++++++++++++++++++++------ |
| 8 | + utils/teamdctl.c | 7 ++- |
| 9 | + 2 files changed, 113 insertions(+), 21 deletions(-) |
| 10 | + |
| 11 | +diff --git a/teamd/teamd_runner_lacp.c b/teamd/teamd_runner_lacp.c |
| 12 | +index 1e5478b..e6676df 100644 |
| 13 | +--- a/teamd/teamd_runner_lacp.c |
| 14 | ++++ b/teamd/teamd_runner_lacp.c |
| 15 | +@@ -213,6 +213,7 @@ enum lacp_port_state { |
| 16 | + PORT_STATE_CURRENT = 1, |
| 17 | + PORT_STATE_EXPIRED = 2, |
| 18 | + PORT_STATE_DEFAULTED = 3, |
| 19 | ++ PORT_STATE_FALLBACK = 4, |
| 20 | + }; |
| 21 | + |
| 22 | + static const char *lacp_port_state_name[] = { |
| 23 | +@@ -220,6 +221,7 @@ static const char *lacp_port_state_name[] = { |
| 24 | + "current", |
| 25 | + "expired", |
| 26 | + "defaulted", |
| 27 | ++ "fallback", |
| 28 | + }; |
| 29 | + |
| 30 | + struct lacp_port { |
| 31 | +@@ -590,24 +592,10 @@ static bool lacp_port_loopback_free(struct lacp_port *lacp_port) |
| 32 | + return true; |
| 33 | + } |
| 34 | + |
| 35 | +-/* |
| 36 | +- * is_lacp_fallback_eligible - is lacp_port eligible to go into lacp fallback mode |
| 37 | +- * |
| 38 | +- * Return true if it is, false otherwise |
| 39 | +- */ |
| 40 | +-static bool is_lacp_fallback_eligible(struct lacp_port *lacp_port) |
| 41 | +-{ |
| 42 | +- teamd_log_dbg(lacp_port->ctx, "%s fallback eligible state \"%d \" cfg \"%d\".", |
| 43 | +- lacp_port->tdport->ifname, lacp_port->state, |
| 44 | +- lacp_port->lacp->cfg.fallback); |
| 45 | +- return lacp_port->state == PORT_STATE_DEFAULTED && |
| 46 | +- lacp_port->lacp->cfg.fallback; |
| 47 | +-} |
| 48 | +- |
| 49 | + static bool lacp_port_selectable_state(struct lacp_port *lacp_port) |
| 50 | + { |
| 51 | + if (lacp_port->state == PORT_STATE_CURRENT || |
| 52 | +- is_lacp_fallback_eligible(lacp_port)) |
| 53 | ++ lacp_port->state == PORT_STATE_FALLBACK) |
| 54 | + return true; |
| 55 | + return false; |
| 56 | + } |
| 57 | +@@ -616,7 +604,7 @@ static bool lacp_port_unselectable_state(struct lacp_port *lacp_port) |
| 58 | + { |
| 59 | + if (lacp_port->state == PORT_STATE_CURRENT || |
| 60 | + lacp_port->state == PORT_STATE_EXPIRED || |
| 61 | +- is_lacp_fallback_eligible(lacp_port)) |
| 62 | ++ lacp_port->state == PORT_STATE_FALLBACK) |
| 63 | + return false; |
| 64 | + return true; |
| 65 | + } |
| 66 | +@@ -633,7 +621,7 @@ static int lacp_port_should_be_enabled(struct lacp_port *lacp_port) |
| 67 | + if (lacp_port_selected(lacp_port) && |
| 68 | + lacp_port->agg_lead == lacp->selected_agg_lead && |
| 69 | + (lacp_port->partner.state & INFO_STATE_SYNCHRONIZATION || |
| 70 | +- is_lacp_fallback_eligible(lacp_port))) |
| 71 | ++ lacp_port->state == PORT_STATE_FALLBACK)) |
| 72 | + return true; |
| 73 | + return false; |
| 74 | + } |
| 75 | +@@ -645,7 +633,7 @@ static int lacp_port_should_be_disabled(struct lacp_port *lacp_port) |
| 76 | + if (!lacp_port_selected(lacp_port) || |
| 77 | + lacp_port->agg_lead != lacp->selected_agg_lead || |
| 78 | + (!(lacp_port->partner.state & INFO_STATE_SYNCHRONIZATION) && |
| 79 | +- !is_lacp_fallback_eligible(lacp_port))) |
| 80 | ++ lacp_port->state != PORT_STATE_FALLBACK)) |
| 81 | + return true; |
| 82 | + return false; |
| 83 | + } |
| 84 | +@@ -1313,7 +1301,7 @@ static void lacp_port_actor_update(struct lacp_port *lacp_port) |
| 85 | + } |
| 86 | + if (lacp_port->state == PORT_STATE_EXPIRED) |
| 87 | + state |= INFO_STATE_EXPIRED; |
| 88 | +- if (lacp_port->state == PORT_STATE_DEFAULTED) |
| 89 | ++ if (lacp_port->state == PORT_STATE_DEFAULTED || lacp_port->state == PORT_STATE_FALLBACK) |
| 90 | + state |= INFO_STATE_DEFAULTED; |
| 91 | + if (teamd_port_count(lacp_port->ctx) > 0) |
| 92 | + state |= INFO_STATE_AGGREGATION; |
| 93 | +@@ -1322,6 +1310,101 @@ static void lacp_port_actor_update(struct lacp_port *lacp_port) |
| 94 | + |
| 95 | + static int lacpdu_send(struct lacp_port *lacp_port); |
| 96 | + |
| 97 | ++static int lacp_port_set_state(struct lacp_port *lacp_port, |
| 98 | ++ enum lacp_port_state new_state); |
| 99 | ++ |
| 100 | ++static bool lacp_port_better_for_fallback(struct lacp_port *lacp_port1, struct lacp_port *lacp_port2) |
| 101 | ++{ |
| 102 | ++ int system_diff; |
| 103 | ++ struct lacpdu_info *actor1; |
| 104 | ++ struct lacpdu_info *actor2; |
| 105 | ++ |
| 106 | ++ if (!lacp_port2) |
| 107 | ++ return true; |
| 108 | ++ |
| 109 | ++ actor1 = &lacp_port1->actor; |
| 110 | ++ actor2 = &lacp_port2->actor; |
| 111 | ++ |
| 112 | ++ system_diff = memcmp(actor1->system, actor2->system, ETH_ALEN); |
| 113 | ++ /* If system MACs differ, the port with the lower system wins. */ |
| 114 | ++ if (system_diff != 0) |
| 115 | ++ return system_diff < 0; |
| 116 | ++ |
| 117 | ++ /* If port priorities differ, the port with the higher priority wins. */ |
| 118 | ++ if (actor2->port_priority != actor1->port_priority) |
| 119 | ++ return (ntohs(actor1->port_priority) > ntohs(actor2->port_priority)); |
| 120 | ++ |
| 121 | ++ /* Otherwise, the port with the lower number wins. */ |
| 122 | ++ return ntohs(actor1->port) < ntohs(actor2->port); |
| 123 | ++} |
| 124 | ++ |
| 125 | ++static int lacp_ports_update_fallback_state(struct lacp *lacp) |
| 126 | ++{ |
| 127 | ++ struct lacp_port *current_fallback = NULL; |
| 128 | ++ struct lacp_port *new_fallback = NULL; |
| 129 | ++ struct teamd_port *tdport; |
| 130 | ++ bool do_fallback = true; |
| 131 | ++ |
| 132 | ++ /* If fallback is disabled, no need to do anything. */ |
| 133 | ++ if (!lacp->cfg.fallback) { |
| 134 | ++ return 0; |
| 135 | ++ } |
| 136 | ++ |
| 137 | ++ /* Check all ports. |
| 138 | ++ * If any port is receiving LACPDUs, we disable fallback altogether. |
| 139 | ++ * Otherwise, the non-disabled port with the highest priority is moved |
| 140 | ++ * to fallback mode. */ |
| 141 | ++ teamd_for_each_tdport(tdport, lacp->ctx) { |
| 142 | ++ struct lacp_port *lacp_port = lacp_port_get(lacp, tdport); |
| 143 | ++ |
| 144 | ++ if (lacp_port->state == PORT_STATE_DISABLED) { |
| 145 | ++ /* Ignore disabled ports. */ |
| 146 | ++ continue; |
| 147 | ++ } |
| 148 | ++ if (lacp_port->state == PORT_STATE_CURRENT || lacp_port->state == PORT_STATE_EXPIRED) { |
| 149 | ++ /* If at least one port is currently receiving LACPDUs, don't do any fallback. */ |
| 150 | ++ do_fallback = false; |
| 151 | ++ continue; |
| 152 | ++ } |
| 153 | ++ if (lacp_port->state == PORT_STATE_FALLBACK) { |
| 154 | ++ /* Remember what the current fallback port is. */ |
| 155 | ++ current_fallback = lacp_port; |
| 156 | ++ } |
| 157 | ++ |
| 158 | ++ /* Port is either defaulted or already fallback, so it's a candidate for new fallback port. |
| 159 | ++ * If there is already a viable candidate, check which one has higher priority. */ |
| 160 | ++ if (lacp_port_better_for_fallback(lacp_port, new_fallback)) { |
| 161 | ++ new_fallback = lacp_port; |
| 162 | ++ } |
| 163 | ++ } |
| 164 | ++ |
| 165 | ++ if (!do_fallback) { |
| 166 | ++ new_fallback = NULL; |
| 167 | ++ } |
| 168 | ++ |
| 169 | ++ if (current_fallback == new_fallback) { |
| 170 | ++ /* Current state is already fine, nothing to do. */ |
| 171 | ++ return 0; |
| 172 | ++ } |
| 173 | ++ |
| 174 | ++ /* Fallback port has changed, move the old one back to defaulted (if there was one) |
| 175 | ++ * and move the new one to fallback (if there is one) |
| 176 | ++ */ |
| 177 | ++ if (current_fallback) { |
| 178 | ++ /* Note: lacp_port_set_state will call back into lacp_ports_update_fallback_state. |
| 179 | ++ * This is fine though: We disable the old fallback, so the second time will find |
| 180 | ++ * that there is no fallback and enable the new one. |
| 181 | ++ * Therefore, after this call, we don't need to set the state of new_fallback here |
| 182 | ++ * as well.*/ |
| 183 | ++ return lacp_port_set_state(current_fallback, PORT_STATE_DEFAULTED); |
| 184 | ++ } |
| 185 | ++ else if (new_fallback) { |
| 186 | ++ return lacp_port_set_state(new_fallback, PORT_STATE_FALLBACK); |
| 187 | ++ } |
| 188 | ++ |
| 189 | ++ return 0; |
| 190 | ++} |
| 191 | ++ |
| 192 | + static int lacp_port_set_state(struct lacp_port *lacp_port, |
| 193 | + enum lacp_port_state new_state) |
| 194 | + { |
| 195 | +@@ -1361,6 +1444,7 @@ static int lacp_port_set_state(struct lacp_port *lacp_port, |
| 196 | + LACP_TIMEOUT_CB_NAME, lacp_port); |
| 197 | + break; |
| 198 | + case PORT_STATE_DEFAULTED: |
| 199 | ++ case PORT_STATE_FALLBACK: |
| 200 | + teamd_loop_callback_disable(lacp_port->ctx, |
| 201 | + LACP_TIMEOUT_CB_NAME, lacp_port); |
| 202 | + memset(&lacp_port->partner, 0, sizeof(lacp_port->partner)); |
| 203 | +@@ -1394,6 +1478,10 @@ static int lacp_port_set_state(struct lacp_port *lacp_port, |
| 204 | + if (err) |
| 205 | + return err; |
| 206 | + |
| 207 | ++ err = lacp_ports_update_fallback_state(lacp_port->lacp); |
| 208 | ++ if (err) |
| 209 | ++ return err; |
| 210 | ++ |
| 211 | + lacp_port_actor_update(lacp_port); |
| 212 | + |
| 213 | + return lacpdu_send(lacp_port); |
| 214 | +@@ -1649,6 +1737,7 @@ static int lacp_callback_timeout(struct teamd_context *ctx, int events, |
| 215 | + err = lacp_port_set_state(lacp_port, PORT_STATE_DEFAULTED); |
| 216 | + break; |
| 217 | + case PORT_STATE_DEFAULTED: |
| 218 | ++ case PORT_STATE_FALLBACK: |
| 219 | + case PORT_STATE_DISABLED: |
| 220 | + /* This can't happen */ |
| 221 | + break; |
| 222 | +diff --git a/utils/teamdctl.c b/utils/teamdctl.c |
| 223 | +index 7fcbfff..d8c71c1 100644 |
| 224 | +--- a/utils/teamdctl.c |
| 225 | ++++ b/utils/teamdctl.c |
| 226 | +@@ -502,12 +502,14 @@ static int stateview_json_runner_process(char *runner_name, json_t *json) |
| 227 | + int active; |
| 228 | + int sys_prio; |
| 229 | + int fast_rate; |
| 230 | ++ int fallback; |
| 231 | + |
| 232 | + pr_out("runner:\n"); |
| 233 | +- err = json_unpack(json, "{s:{s:b, s:i, s:b}}", "runner", |
| 234 | ++ err = json_unpack(json, "{s:{s:b, s:i, s:b, s:b}}", "runner", |
| 235 | + "active", &active, |
| 236 | + "sys_prio", &sys_prio, |
| 237 | +- "fast_rate", &fast_rate); |
| 238 | ++ "fast_rate", &fast_rate, |
| 239 | ++ "fallback", &fallback); |
| 240 | + if (err) { |
| 241 | + pr_err("Failed to parse JSON runner dump.\n"); |
| 242 | + return -EINVAL; |
| 243 | +@@ -515,6 +517,7 @@ static int stateview_json_runner_process(char *runner_name, json_t *json) |
| 244 | + pr_out_indent_inc(); |
| 245 | + pr_out("active: %s\n", boolyesno(active)); |
| 246 | + pr_out("fast rate: %s\n", boolyesno(fast_rate)); |
| 247 | ++ pr_out("fallback: %s\n", boolyesno(fallback)); |
| 248 | + pr_out2("system priority: %d\n", sys_prio); |
| 249 | + pr_out_indent_dec(); |
| 250 | + } |
| 251 | +-- |
| 252 | +2.41.0 |
| 253 | + |
0 commit comments