]>
Commit | Line | Data |
---|---|---|
5617ae6a | 1 | /* Copyright (c) 2011, 2012, 2013, 2014, 2015 Nicira, Inc. |
6aa74308 EJ |
2 | * |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at: | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | #include <config.h> | |
17 | #include "lacp.h" | |
18 | ||
6aa74308 EJ |
19 | #include <stdlib.h> |
20 | ||
f23d157c | 21 | #include "connectivity.h" |
3e8a2ad1 | 22 | #include "openvswitch/dynamic-string.h" |
6aa74308 | 23 | #include "hash.h" |
ee89ea7b | 24 | #include "openvswitch/hmap.h" |
cf62fa4c | 25 | #include "dp-packet.h" |
5617ae6a | 26 | #include "ovs-atomic.h" |
6aa74308 | 27 | #include "packets.h" |
fd016ae3 | 28 | #include "openvswitch/poll-loop.h" |
f23d157c | 29 | #include "seq.h" |
ee89ea7b | 30 | #include "openvswitch/shash.h" |
20834809 | 31 | #include "timer.h" |
6aa74308 EJ |
32 | #include "timeval.h" |
33 | #include "unixctl.h" | |
e6211adc | 34 | #include "openvswitch/vlog.h" |
ee89ea7b | 35 | #include "util.h" |
6aa74308 EJ |
36 | |
37 | VLOG_DEFINE_THIS_MODULE(lacp); | |
38 | ||
5f877369 EJ |
39 | /* Masks for lacp_info state member. */ |
40 | #define LACP_STATE_ACT 0x01 /* Activity. Active or passive? */ | |
41 | #define LACP_STATE_TIME 0x02 /* Timeout. Short or long timeout? */ | |
42 | #define LACP_STATE_AGG 0x04 /* Aggregation. Is the link is bondable? */ | |
43 | #define LACP_STATE_SYNC 0x08 /* Synchronization. Is the link in up to date? */ | |
44 | #define LACP_STATE_COL 0x10 /* Collecting. Is the link receiving frames? */ | |
45 | #define LACP_STATE_DIST 0x20 /* Distributing. Is the link sending frames? */ | |
46 | #define LACP_STATE_DEF 0x40 /* Defaulted. Using default partner info? */ | |
47 | #define LACP_STATE_EXP 0x80 /* Expired. Using expired partner info? */ | |
48 | ||
49 | #define LACP_FAST_TIME_TX 1000 /* Fast transmission rate. */ | |
50 | #define LACP_SLOW_TIME_TX 30000 /* Slow transmission rate. */ | |
51 | #define LACP_RX_MULTIPLIER 3 /* Multiply by TX rate to get RX rate. */ | |
52 | ||
53 | #define LACP_INFO_LEN 15 | |
13b6bae6 | 54 | OVS_PACKED( |
5f877369 EJ |
55 | struct lacp_info { |
56 | ovs_be16 sys_priority; /* System priority. */ | |
74ff3298 | 57 | struct eth_addr sys_id; /* System ID. */ |
5f877369 EJ |
58 | ovs_be16 key; /* Operational key. */ |
59 | ovs_be16 port_priority; /* Port priority. */ | |
60 | ovs_be16 port_id; /* Port ID. */ | |
61 | uint8_t state; /* State mask. See LACP_STATE macros. */ | |
13b6bae6 | 62 | }); |
5f877369 EJ |
63 | BUILD_ASSERT_DECL(LACP_INFO_LEN == sizeof(struct lacp_info)); |
64 | ||
65 | #define LACP_PDU_LEN 110 | |
66 | struct lacp_pdu { | |
67 | uint8_t subtype; /* Always 1. */ | |
68 | uint8_t version; /* Always 1. */ | |
69 | ||
70 | uint8_t actor_type; /* Always 1. */ | |
71 | uint8_t actor_len; /* Always 20. */ | |
72 | struct lacp_info actor; /* LACP actor information. */ | |
73 | uint8_t z1[3]; /* Reserved. Always 0. */ | |
74 | ||
75 | uint8_t partner_type; /* Always 2. */ | |
76 | uint8_t partner_len; /* Always 20. */ | |
77 | struct lacp_info partner; /* LACP partner information. */ | |
78 | uint8_t z2[3]; /* Reserved. Always 0. */ | |
79 | ||
80 | uint8_t collector_type; /* Always 3. */ | |
81 | uint8_t collector_len; /* Always 16. */ | |
82 | ovs_be16 collector_delay; /* Maximum collector delay. Set to UINT16_MAX. */ | |
83 | uint8_t z3[64]; /* Combination of several fields. Always 0. */ | |
f303f87e | 84 | }; |
5f877369 EJ |
85 | BUILD_ASSERT_DECL(LACP_PDU_LEN == sizeof(struct lacp_pdu)); |
86 | \f | |
87 | /* Implementation. */ | |
88 | ||
9248e103 N |
89 | enum pdu_subtype { |
90 | SUBTYPE_UNUSED = 0, | |
91 | SUBTYPE_LACP, /* Link Aggregation Control Protocol. */ | |
92 | SUBTYPE_MARKER, /* Link Aggregation Marker Protocol. */ | |
93 | }; | |
94 | ||
91fc374a | 95 | enum member_status { |
6aa74308 EJ |
96 | LACP_CURRENT, /* Current State. Partner up to date. */ |
97 | LACP_EXPIRED, /* Expired State. Partner out of date. */ | |
98 | LACP_DEFAULTED, /* Defaulted State. No partner. */ | |
99 | }; | |
100 | ||
91fc374a | 101 | /* A LACP primary interface. */ |
6aa74308 | 102 | struct lacp { |
ca6ba700 | 103 | struct ovs_list node; /* Node in all_lacps list. */ |
6aa74308 | 104 | char *name; /* Name of this lacp object. */ |
74ff3298 | 105 | struct eth_addr sys_id; /* System ID. */ |
6aa74308 EJ |
106 | uint16_t sys_priority; /* System Priority. */ |
107 | bool active; /* Active or Passive. */ | |
108 | ||
91fc374a BP |
109 | struct hmap members; /* Members this LACP object controls. */ |
110 | struct member *key_member; /* Member whose ID will be aggregation key. */ | |
6aa74308 | 111 | |
bf83f7c8 | 112 | bool fast; /* True if using fast probe interval. */ |
6aa74308 EJ |
113 | bool negotiated; /* True if LACP negotiations were successful. */ |
114 | bool update; /* True if lacp_update() needs to be called. */ | |
9dd165e0 | 115 | bool fallback_ab; /* True if fallback to active-backup on LACP failure. */ |
91779071 | 116 | |
37bec3d3 | 117 | struct ovs_refcount ref_cnt; |
6aa74308 EJ |
118 | }; |
119 | ||
91fc374a BP |
120 | /* A LACP member interface. */ |
121 | struct member { | |
122 | void *aux; /* Handle used to identify this member. */ | |
123 | struct hmap_node node; /* Node in primary's members map. */ | |
6aa74308 | 124 | |
91fc374a | 125 | struct lacp *lacp; /* LACP object containing this member. */ |
6aa74308 EJ |
126 | uint16_t port_id; /* Port ID. */ |
127 | uint16_t port_priority; /* Port Priority. */ | |
e1ce3f2d | 128 | uint16_t key; /* Aggregation Key. 0 if default. */ |
91fc374a | 129 | char *name; /* Name of this member. */ |
6aa74308 | 130 | |
91fc374a | 131 | enum member_status status; /* Member status. */ |
6aa74308 | 132 | bool attached; /* Attached. Traffic may flow. */ |
b3e8cd6b | 133 | bool carrier_up; /* Carrier state of link. */ |
6aa74308 | 134 | struct lacp_info partner; /* Partner information. */ |
77fdfa9b | 135 | struct lacp_info ntt_actor; /* Used to decide if we Need To Transmit. */ |
20834809 EJ |
136 | struct timer tx; /* Next message transmission timer. */ |
137 | struct timer rx; /* Expected message receive timer. */ | |
50b9699f | 138 | |
49b9cad3 NK |
139 | uint32_t count_rx_pdus; /* dot3adAggPortStatsLACPDUsRx */ |
140 | uint32_t count_rx_pdus_bad; /* dot3adAggPortStatsIllegalRx */ | |
9248e103 | 141 | uint32_t count_rx_pdus_marker; /* dot3adAggPortStatsMarkerPDUsRx */ |
49b9cad3 NK |
142 | uint32_t count_tx_pdus; /* dot3adAggPortStatsLACPDUsTx */ |
143 | uint32_t count_link_expired; /* Num of times link expired */ | |
144 | uint32_t count_link_defaulted; /* Num of times link defaulted */ | |
145 | uint32_t count_carrier_changed; /* Num of times link status changed */ | |
6aa74308 EJ |
146 | }; |
147 | ||
b468782a | 148 | static struct ovs_mutex mutex; |
55951e15 | 149 | static struct ovs_list all_lacps__ = OVS_LIST_INITIALIZER(&all_lacps__); |
ca6ba700 | 150 | static struct ovs_list *const all_lacps OVS_GUARDED_BY(mutex) = &all_lacps__; |
b468782a | 151 | |
bd3950dd | 152 | static void lacp_update_attached(struct lacp *) OVS_REQUIRES(mutex); |
b468782a | 153 | |
91fc374a BP |
154 | static void member_destroy(struct member *) OVS_REQUIRES(mutex); |
155 | static void member_set_defaulted(struct member *) OVS_REQUIRES(mutex); | |
156 | static void member_set_expired(struct member *) OVS_REQUIRES(mutex); | |
157 | static void member_get_actor(struct member *, struct lacp_info *actor) | |
bd3950dd | 158 | OVS_REQUIRES(mutex); |
91fc374a | 159 | static void member_get_priority(struct member *, struct lacp_info *priority) |
bd3950dd | 160 | OVS_REQUIRES(mutex); |
91fc374a | 161 | static bool member_may_tx(const struct member *) |
bd3950dd | 162 | OVS_REQUIRES(mutex); |
91fc374a | 163 | static struct member *member_lookup(const struct lacp *, const void *member) |
bd3950dd | 164 | OVS_REQUIRES(mutex); |
b468782a | 165 | static bool info_tx_equal(struct lacp_info *, struct lacp_info *) |
bd3950dd | 166 | OVS_REQUIRES(mutex); |
91fc374a | 167 | static bool member_may_enable__(struct member *) OVS_REQUIRES(mutex); |
6aa74308 | 168 | |
0e15264f | 169 | static unixctl_cb_func lacp_unixctl_show; |
49b9cad3 | 170 | static unixctl_cb_func lacp_unixctl_show_stats; |
6aa74308 | 171 | |
81aee5f9 | 172 | /* Populates 'pdu' with a LACP PDU comprised of 'actor' and 'partner'. */ |
5f877369 | 173 | static void |
81aee5f9 EJ |
174 | compose_lacp_pdu(const struct lacp_info *actor, |
175 | const struct lacp_info *partner, struct lacp_pdu *pdu) | |
176 | { | |
177 | memset(pdu, 0, sizeof *pdu); | |
178 | ||
179 | pdu->subtype = 1; | |
180 | pdu->version = 1; | |
181 | ||
182 | pdu->actor_type = 1; | |
183 | pdu->actor_len = 20; | |
184 | pdu->actor = *actor; | |
185 | ||
186 | pdu->partner_type = 2; | |
187 | pdu->partner_len = 20; | |
188 | pdu->partner = *partner; | |
189 | ||
190 | pdu->collector_type = 3; | |
191 | pdu->collector_len = 16; | |
192 | pdu->collector_delay = htons(0); | |
193 | } | |
194 | ||
9248e103 N |
195 | /* Parses 'p' which represents a packet containing a LACP PDU. This function |
196 | * returns NULL if 'p' is malformed, or does not represent a LACP PDU format | |
81aee5f9 | 197 | * supported by OVS. Otherwise, it returns a pointer to the lacp_pdu contained |
9248e103 N |
198 | * within 'p'. It also returns the subtype of PDU.*/ |
199 | ||
5f877369 | 200 | static const struct lacp_pdu * |
9248e103 | 201 | parse_lacp_packet(const struct dp_packet *p, enum pdu_subtype *subtype) |
81aee5f9 EJ |
202 | { |
203 | const struct lacp_pdu *pdu; | |
204 | ||
cf62fa4c | 205 | pdu = dp_packet_at(p, (uint8_t *)dp_packet_l3(p) - (uint8_t *)dp_packet_data(p), |
437d0d22 | 206 | LACP_PDU_LEN); |
81aee5f9 EJ |
207 | |
208 | if (pdu && pdu->subtype == 1 | |
209 | && pdu->actor_type == 1 && pdu->actor_len == 20 | |
210 | && pdu->partner_type == 2 && pdu->partner_len == 20) { | |
9248e103 | 211 | *subtype = SUBTYPE_LACP; |
81aee5f9 | 212 | return pdu; |
9248e103 N |
213 | } else if (pdu && pdu->subtype == SUBTYPE_MARKER) { |
214 | *subtype = SUBTYPE_MARKER; | |
215 | return NULL; | |
216 | } else{ | |
217 | *subtype = SUBTYPE_UNUSED; | |
81aee5f9 EJ |
218 | return NULL; |
219 | } | |
220 | } | |
221 | \f | |
222 | /* LACP Protocol Implementation. */ | |
223 | ||
6aa74308 EJ |
224 | /* Initializes the lacp module. */ |
225 | void | |
226 | lacp_init(void) | |
227 | { | |
0e15264f BP |
228 | unixctl_command_register("lacp/show", "[port]", 0, 1, |
229 | lacp_unixctl_show, NULL); | |
49b9cad3 NK |
230 | unixctl_command_register("lacp/show-stats", "[port]", 0, 1, |
231 | lacp_unixctl_show_stats, NULL); | |
6aa74308 EJ |
232 | } |
233 | ||
2339e869 BP |
234 | static void |
235 | lacp_lock(void) OVS_ACQUIRES(mutex) | |
6aa74308 | 236 | { |
b468782a | 237 | static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; |
6aa74308 | 238 | |
b468782a | 239 | if (ovsthread_once_start(&once)) { |
834d6caf | 240 | ovs_mutex_init_recursive(&mutex); |
b468782a EJ |
241 | ovsthread_once_done(&once); |
242 | } | |
2339e869 BP |
243 | ovs_mutex_lock(&mutex); |
244 | } | |
245 | ||
246 | static void | |
247 | lacp_unlock(void) OVS_RELEASES(mutex) | |
248 | { | |
249 | ovs_mutex_unlock(&mutex); | |
250 | } | |
251 | ||
252 | /* Creates a LACP object. */ | |
253 | struct lacp * | |
254 | lacp_create(void) OVS_EXCLUDED(mutex) | |
255 | { | |
256 | struct lacp *lacp; | |
b468782a | 257 | |
6aa74308 | 258 | lacp = xzalloc(sizeof *lacp); |
91fc374a | 259 | hmap_init(&lacp->members); |
37bec3d3 | 260 | ovs_refcount_init(&lacp->ref_cnt); |
b468782a | 261 | |
2339e869 | 262 | lacp_lock(); |
417e7e66 | 263 | ovs_list_push_back(all_lacps, &lacp->node); |
2339e869 | 264 | lacp_unlock(); |
91779071 EJ |
265 | return lacp; |
266 | } | |
267 | ||
268 | struct lacp * | |
269 | lacp_ref(const struct lacp *lacp_) | |
270 | { | |
271 | struct lacp *lacp = CONST_CAST(struct lacp *, lacp_); | |
272 | if (lacp) { | |
37bec3d3 | 273 | ovs_refcount_ref(&lacp->ref_cnt); |
91779071 | 274 | } |
6aa74308 EJ |
275 | return lacp; |
276 | } | |
277 | ||
91fc374a | 278 | /* Destroys 'lacp' and its members. Does nothing if 'lacp' is NULL. */ |
6aa74308 | 279 | void |
b468782a | 280 | lacp_unref(struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 | 281 | { |
24f83812 | 282 | if (lacp && ovs_refcount_unref_relaxed(&lacp->ref_cnt) == 1) { |
91fc374a | 283 | struct member *member, *next; |
6aa74308 | 284 | |
2339e869 | 285 | lacp_lock(); |
91fc374a BP |
286 | HMAP_FOR_EACH_SAFE (member, next, node, &lacp->members) { |
287 | member_destroy(member); | |
6aa74308 EJ |
288 | } |
289 | ||
91fc374a | 290 | hmap_destroy(&lacp->members); |
417e7e66 | 291 | ovs_list_remove(&lacp->node); |
6aa74308 EJ |
292 | free(lacp->name); |
293 | free(lacp); | |
2339e869 | 294 | lacp_unlock(); |
6aa74308 EJ |
295 | } |
296 | } | |
297 | ||
bb5bc6c0 | 298 | /* Configures 'lacp' with settings from 's'. */ |
6aa74308 | 299 | void |
bb5bc6c0 | 300 | lacp_configure(struct lacp *lacp, const struct lacp_settings *s) |
b468782a | 301 | OVS_EXCLUDED(mutex) |
6aa74308 | 302 | { |
cb22974d | 303 | ovs_assert(!eth_addr_is_zero(s->id)); |
69fdadb1 | 304 | |
2339e869 | 305 | lacp_lock(); |
bb5bc6c0 | 306 | if (!lacp->name || strcmp(s->name, lacp->name)) { |
6aa74308 | 307 | free(lacp->name); |
bb5bc6c0 | 308 | lacp->name = xstrdup(s->name); |
6aa74308 EJ |
309 | } |
310 | ||
eb458893 | 311 | if (!eth_addr_equals(lacp->sys_id, s->id) |
b20a8f7c | 312 | || lacp->sys_priority != s->priority) { |
74ff3298 | 313 | lacp->sys_id = s->id; |
eb458893 EJ |
314 | lacp->sys_priority = s->priority; |
315 | lacp->update = true; | |
316 | } | |
317 | ||
bb5bc6c0 | 318 | lacp->active = s->active; |
bf83f7c8 | 319 | lacp->fast = s->fast; |
9dd165e0 RK |
320 | |
321 | if (lacp->fallback_ab != s->fallback_ab_cfg) { | |
322 | lacp->fallback_ab = s->fallback_ab_cfg; | |
323 | lacp->update = true; | |
324 | } | |
325 | ||
2339e869 | 326 | lacp_unlock(); |
6aa74308 EJ |
327 | } |
328 | ||
7a673515 BP |
329 | /* Returns true if 'lacp' is configured in active mode, false if 'lacp' is |
330 | * configured for passive mode. */ | |
331 | bool | |
b468782a | 332 | lacp_is_active(const struct lacp *lacp) OVS_EXCLUDED(mutex) |
7a673515 | 333 | { |
b468782a | 334 | bool ret; |
2339e869 | 335 | lacp_lock(); |
b468782a | 336 | ret = lacp->active; |
2339e869 | 337 | lacp_unlock(); |
b468782a | 338 | return ret; |
7a673515 BP |
339 | } |
340 | ||
91fc374a BP |
341 | /* Processes 'packet' which was received on 'member_'. This function should be |
342 | * called on all packets received on 'member_' with Ethernet Type | |
343 | * ETH_TYPE_LACP. | |
5f877369 | 344 | */ |
a8448cb1 | 345 | bool |
91fc374a | 346 | lacp_process_packet(struct lacp *lacp, const void *member_, |
cf62fa4c | 347 | const struct dp_packet *packet) |
b468782a | 348 | OVS_EXCLUDED(mutex) |
6aa74308 | 349 | { |
5f877369 | 350 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
5f877369 | 351 | const struct lacp_pdu *pdu; |
cdcf42c6 | 352 | long long int tx_rate; |
91fc374a | 353 | struct member *member; |
a8448cb1 | 354 | bool lacp_may_enable = false; |
9248e103 | 355 | enum pdu_subtype subtype; |
cdcf42c6 | 356 | |
2339e869 | 357 | lacp_lock(); |
91fc374a BP |
358 | member = member_lookup(lacp, member_); |
359 | if (!member) { | |
b468782a | 360 | goto out; |
f1ce3514 | 361 | } |
91fc374a | 362 | member->count_rx_pdus++; |
f1ce3514 | 363 | |
9248e103 N |
364 | pdu = parse_lacp_packet(packet, &subtype); |
365 | switch (subtype) { | |
366 | case SUBTYPE_LACP: | |
367 | break; | |
368 | case SUBTYPE_MARKER: | |
91fc374a | 369 | member->count_rx_pdus_marker++; |
9248e103 N |
370 | VLOG_DBG("%s: received a LACP marker PDU.", lacp->name); |
371 | goto out; | |
372 | case SUBTYPE_UNUSED: | |
373 | default: | |
91fc374a | 374 | member->count_rx_pdus_bad++; |
9248e103 N |
375 | VLOG_WARN_RL(&rl, "%s: received an unparsable LACP PDU.", |
376 | lacp->name); | |
377 | goto out; | |
5f877369 EJ |
378 | } |
379 | ||
b3e8cd6b NK |
380 | /* On some NICs L1 state reporting is slow. In case LACP packets are |
381 | * received while carrier (L1) state is still down, drop the LACP PDU and | |
382 | * trigger re-checking of L1 state. */ | |
91fc374a | 383 | if (!member->carrier_up) { |
b3e8cd6b | 384 | VLOG_INFO_RL(&rl, "%s: carrier state is DOWN," |
91fc374a | 385 | " dropping received LACP PDU.", member->name); |
b3e8cd6b NK |
386 | seq_change(connectivity_seq_get()); |
387 | goto out; | |
388 | } | |
389 | ||
91fc374a | 390 | member->status = LACP_CURRENT; |
bf83f7c8 | 391 | tx_rate = lacp->fast ? LACP_FAST_TIME_TX : LACP_SLOW_TIME_TX; |
91fc374a | 392 | timer_set_duration(&member->rx, LACP_RX_MULTIPLIER * tx_rate); |
6aa74308 | 393 | |
91fc374a | 394 | member->ntt_actor = pdu->partner; |
6aa74308 | 395 | |
9248e103 | 396 | /* Update our information about our partner if it's out of date. This may |
6aa74308 | 397 | * cause priorities to change so re-calculate attached status of all |
91fc374a BP |
398 | * members. */ |
399 | if (memcmp(&member->partner, &pdu->actor, sizeof pdu->actor)) { | |
6aa74308 | 400 | lacp->update = true; |
91fc374a | 401 | member->partner = pdu->actor; |
6aa74308 | 402 | } |
b468782a | 403 | |
a8448cb1 NK |
404 | /* Evaluate may_enable here to avoid dropping of packets till main thread |
405 | * sets may_enable to true. */ | |
91fc374a | 406 | lacp_may_enable = member_may_enable__(member); |
a8448cb1 | 407 | |
b468782a | 408 | out: |
2339e869 | 409 | lacp_unlock(); |
a8448cb1 NK |
410 | |
411 | return lacp_may_enable; | |
6aa74308 EJ |
412 | } |
413 | ||
bdebeece EJ |
414 | /* Returns the lacp_status of the given 'lacp' object (which may be NULL). */ |
415 | enum lacp_status | |
b468782a | 416 | lacp_status(const struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 | 417 | { |
2a3fb0aa BP |
418 | if (lacp) { |
419 | enum lacp_status ret; | |
b468782a | 420 | |
2339e869 | 421 | lacp_lock(); |
2a3fb0aa | 422 | ret = lacp->negotiated ? LACP_NEGOTIATED : LACP_CONFIGURED; |
2339e869 | 423 | lacp_unlock(); |
2a3fb0aa | 424 | return ret; |
bdebeece | 425 | } else { |
2a3fb0aa BP |
426 | /* Don't take 'mutex'. It might not even be initialized, since we |
427 | * don't know that any lacp object has been created. */ | |
428 | return LACP_DISABLED; | |
bdebeece | 429 | } |
6aa74308 EJ |
430 | } |
431 | ||
91fc374a BP |
432 | /* Registers 'member_' as subordinate to 'lacp'. This should be called at |
433 | * least once per member in a LACP managed bond. Should also be called | |
434 | * whenever a member's settings change. */ | |
6aa74308 | 435 | void |
91fc374a BP |
436 | lacp_member_register(struct lacp *lacp, void *member_, |
437 | const struct lacp_member_settings *s) | |
b468782a | 438 | OVS_EXCLUDED(mutex) |
6aa74308 | 439 | { |
91fc374a | 440 | struct member *member; |
6aa74308 | 441 | |
2339e869 | 442 | lacp_lock(); |
91fc374a BP |
443 | member = member_lookup(lacp, member_); |
444 | if (!member) { | |
445 | member = xzalloc(sizeof *member); | |
446 | member->lacp = lacp; | |
447 | member->aux = member_; | |
448 | hmap_insert(&lacp->members, &member->node, hash_pointer(member_, 0)); | |
449 | member_set_defaulted(member); | |
450 | ||
451 | if (!lacp->key_member) { | |
452 | lacp->key_member = member; | |
6aa74308 EJ |
453 | } |
454 | } | |
455 | ||
91fc374a BP |
456 | if (!member->name || strcmp(s->name, member->name)) { |
457 | free(member->name); | |
458 | member->name = xstrdup(s->name); | |
6aa74308 EJ |
459 | } |
460 | ||
91fc374a BP |
461 | if (member->port_id != s->id |
462 | || member->port_priority != s->priority | |
463 | || member->key != s->key) { | |
464 | member->port_id = s->id; | |
465 | member->port_priority = s->priority; | |
466 | member->key = s->key; | |
6aa74308 | 467 | |
6aa74308 EJ |
468 | lacp->update = true; |
469 | ||
470 | if (lacp->active || lacp->negotiated) { | |
91fc374a | 471 | member_set_expired(member); |
6aa74308 EJ |
472 | } |
473 | } | |
2339e869 | 474 | lacp_unlock(); |
6aa74308 EJ |
475 | } |
476 | ||
91fc374a | 477 | /* Unregisters 'member_' with 'lacp'. */ |
6aa74308 | 478 | void |
91fc374a | 479 | lacp_member_unregister(struct lacp *lacp, const void *member_) |
b468782a | 480 | OVS_EXCLUDED(mutex) |
6aa74308 | 481 | { |
91fc374a | 482 | struct member *member; |
6aa74308 | 483 | |
2339e869 | 484 | lacp_lock(); |
91fc374a BP |
485 | member = member_lookup(lacp, member_); |
486 | if (member) { | |
487 | member_destroy(member); | |
eb458893 | 488 | lacp->update = true; |
6aa74308 | 489 | } |
2339e869 | 490 | lacp_unlock(); |
6aa74308 EJ |
491 | } |
492 | ||
91fc374a | 493 | /* This function should be called whenever the carrier status of 'member_' has |
3e5b3fdb | 494 | * changed. If 'lacp' is null, this function has no effect.*/ |
6aa74308 | 495 | void |
91fc374a BP |
496 | lacp_member_carrier_changed(const struct lacp *lacp, const void *member_, |
497 | bool carrier_up) | |
b468782a | 498 | OVS_EXCLUDED(mutex) |
6aa74308 | 499 | { |
91fc374a | 500 | struct member *member; |
b468782a EJ |
501 | if (!lacp) { |
502 | return; | |
503 | } | |
6aa74308 | 504 | |
2339e869 | 505 | lacp_lock(); |
91fc374a BP |
506 | member = member_lookup(lacp, member_); |
507 | if (!member) { | |
b468782a EJ |
508 | goto out; |
509 | } | |
f1ce3514 | 510 | |
91fc374a BP |
511 | if (member->status == LACP_CURRENT || member->lacp->active) { |
512 | member_set_expired(member); | |
6aa74308 | 513 | } |
b3e8cd6b | 514 | |
91fc374a BP |
515 | if (member->carrier_up != carrier_up) { |
516 | member->carrier_up = carrier_up; | |
517 | member->count_carrier_changed++; | |
b3e8cd6b | 518 | } |
b468782a EJ |
519 | |
520 | out: | |
2339e869 | 521 | lacp_unlock(); |
6aa74308 EJ |
522 | } |
523 | ||
29985e75 | 524 | static bool |
91fc374a | 525 | member_may_enable__(struct member *member) OVS_REQUIRES(mutex) |
29985e75 | 526 | { |
91fc374a | 527 | /* The member may be enabled if it's attached to an aggregator and its |
29985e75 | 528 | * partner is synchronized.*/ |
91fc374a BP |
529 | return member->attached && (member->partner.state & LACP_STATE_SYNC |
530 | || (member->lacp && member->lacp->fallback_ab | |
531 | && member->status == LACP_DEFAULTED)); | |
29985e75 EJ |
532 | } |
533 | ||
91fc374a BP |
534 | /* This function should be called before enabling 'member_' to send or receive |
535 | * traffic. If it returns false, 'member_' should not enabled. As a | |
6aa74308 EJ |
536 | * convenience, returns true if 'lacp' is NULL. */ |
537 | bool | |
91fc374a | 538 | lacp_member_may_enable(const struct lacp *lacp, const void *member_) |
b468782a | 539 | OVS_EXCLUDED(mutex) |
6aa74308 EJ |
540 | { |
541 | if (lacp) { | |
91fc374a | 542 | struct member *member; |
b3e8cd6b | 543 | bool ret = false; |
b468782a | 544 | |
2339e869 | 545 | lacp_lock(); |
91fc374a BP |
546 | member = member_lookup(lacp, member_); |
547 | if (member) { | |
548 | /* It is only called when carrier is up. So, enable member's | |
b3e8cd6b | 549 | * carrier state if it is currently down. */ |
91fc374a BP |
550 | if (!member->carrier_up) { |
551 | member->carrier_up = true; | |
b3e8cd6b | 552 | } |
91fc374a | 553 | ret = member_may_enable__(member); |
b3e8cd6b | 554 | } |
2339e869 | 555 | lacp_unlock(); |
b468782a | 556 | return ret; |
6aa74308 EJ |
557 | } else { |
558 | return true; | |
559 | } | |
560 | } | |
561 | ||
91fc374a | 562 | /* Returns true if partner information on 'member_' is up to date. 'member_' |
44cb163f EJ |
563 | * not being current, generally indicates a connectivity problem, or a |
564 | * misconfigured (or broken) partner. */ | |
565 | bool | |
91fc374a | 566 | lacp_member_is_current(const struct lacp *lacp, const void *member_) |
b468782a | 567 | OVS_EXCLUDED(mutex) |
44cb163f | 568 | { |
91fc374a | 569 | struct member *member; |
b468782a EJ |
570 | bool ret; |
571 | ||
2339e869 | 572 | lacp_lock(); |
91fc374a BP |
573 | member = member_lookup(lacp, member_); |
574 | ret = member ? member->status != LACP_DEFAULTED : false; | |
2339e869 | 575 | lacp_unlock(); |
b468782a | 576 | return ret; |
44cb163f EJ |
577 | } |
578 | ||
6aa74308 EJ |
579 | /* This function should be called periodically to update 'lacp'. */ |
580 | void | |
b468782a | 581 | lacp_run(struct lacp *lacp, lacp_send_pdu *send_pdu) OVS_EXCLUDED(mutex) |
6aa74308 | 582 | { |
91fc374a | 583 | struct member *member; |
6aa74308 | 584 | |
2339e869 | 585 | lacp_lock(); |
91fc374a BP |
586 | HMAP_FOR_EACH (member, node, &lacp->members) { |
587 | if (timer_expired(&member->rx)) { | |
588 | enum member_status old_status = member->status; | |
589 | ||
590 | if (member->status == LACP_CURRENT) { | |
591 | member_set_expired(member); | |
592 | member->count_link_expired++; | |
593 | } else if (member->status == LACP_EXPIRED) { | |
594 | member_set_defaulted(member); | |
595 | member->count_link_defaulted++; | |
6aa74308 | 596 | } |
91fc374a | 597 | if (member->status != old_status) { |
f23d157c JS |
598 | seq_change(connectivity_seq_get()); |
599 | } | |
6aa74308 | 600 | } |
6aa74308 EJ |
601 | } |
602 | ||
603 | if (lacp->update) { | |
604 | lacp_update_attached(lacp); | |
8b0c3c38 | 605 | seq_change(connectivity_seq_get()); |
6aa74308 EJ |
606 | } |
607 | ||
91fc374a | 608 | HMAP_FOR_EACH (member, node, &lacp->members) { |
77fdfa9b | 609 | struct lacp_info actor; |
6aa74308 | 610 | |
91fc374a | 611 | if (!member_may_tx(member)) { |
6aa74308 EJ |
612 | continue; |
613 | } | |
614 | ||
91fc374a | 615 | member_get_actor(member, &actor); |
77fdfa9b | 616 | |
91fc374a BP |
617 | if (timer_expired(&member->tx) |
618 | || !info_tx_equal(&actor, &member->ntt_actor)) { | |
cdcf42c6 | 619 | long long int duration; |
5f877369 | 620 | struct lacp_pdu pdu; |
77fdfa9b | 621 | |
91fc374a BP |
622 | member->ntt_actor = actor; |
623 | compose_lacp_pdu(&actor, &member->partner, &pdu); | |
624 | send_pdu(member->aux, &pdu, sizeof pdu); | |
625 | member->count_tx_pdus++; | |
6aa74308 | 626 | |
91fc374a | 627 | duration = (member->partner.state & LACP_STATE_TIME |
bf83f7c8 EJ |
628 | ? LACP_FAST_TIME_TX |
629 | : LACP_SLOW_TIME_TX); | |
cdcf42c6 | 630 | |
91fc374a | 631 | timer_set_duration(&member->tx, duration); |
f23d157c | 632 | seq_change(connectivity_seq_get()); |
77fdfa9b | 633 | } |
6aa74308 | 634 | } |
2339e869 | 635 | lacp_unlock(); |
6aa74308 EJ |
636 | } |
637 | ||
638 | /* Causes poll_block() to wake up when lacp_run() needs to be called again. */ | |
639 | void | |
b468782a | 640 | lacp_wait(struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 | 641 | { |
91fc374a | 642 | struct member *member; |
6aa74308 | 643 | |
2339e869 | 644 | lacp_lock(); |
91fc374a BP |
645 | HMAP_FOR_EACH (member, node, &lacp->members) { |
646 | if (member_may_tx(member)) { | |
647 | timer_wait(&member->tx); | |
6aa74308 EJ |
648 | } |
649 | ||
91fc374a BP |
650 | if (member->status != LACP_DEFAULTED) { |
651 | timer_wait(&member->rx); | |
6aa74308 EJ |
652 | } |
653 | } | |
2339e869 | 654 | lacp_unlock(); |
6aa74308 EJ |
655 | } |
656 | \f | |
657 | /* Static Helpers. */ | |
658 | ||
91fc374a BP |
659 | /* Updates the attached status of all members controlled by 'lacp' and sets its |
660 | * negotiated parameter to true if any members are attachable. */ | |
6aa74308 | 661 | static void |
bd3950dd | 662 | lacp_update_attached(struct lacp *lacp) OVS_REQUIRES(mutex) |
6aa74308 | 663 | { |
91fc374a | 664 | struct member *lead, *lead_current, *member; |
6aa74308 | 665 | struct lacp_info lead_pri; |
86c8462d | 666 | bool lead_enable; |
6aa74308 EJ |
667 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 10); |
668 | ||
669 | lacp->update = false; | |
670 | ||
671 | lead = NULL; | |
6f50056c | 672 | lead_current = NULL; |
86c8462d | 673 | lead_enable = false; |
6f50056c RM |
674 | |
675 | /* Check if there is a working interface. | |
676 | * Store as lead_current, if there is one. */ | |
91fc374a BP |
677 | HMAP_FOR_EACH (member, node, &lacp->members) { |
678 | if (member->status == LACP_CURRENT && member->attached) { | |
6f50056c | 679 | struct lacp_info pri; |
91fc374a | 680 | member_get_priority(member, &pri); |
6f50056c | 681 | if (!lead_current || memcmp(&pri, &lead_pri, sizeof pri) < 0) { |
91fc374a | 682 | lead_current = member; |
6f50056c RM |
683 | lead = lead_current; |
684 | lead_pri = pri; | |
685 | lead_enable = true; | |
686 | } | |
687 | } | |
688 | } | |
689 | ||
690 | /* Find interface with highest priority. */ | |
91fc374a | 691 | HMAP_FOR_EACH (member, node, &lacp->members) { |
6aa74308 EJ |
692 | struct lacp_info pri; |
693 | ||
91fc374a | 694 | member->attached = false; |
6aa74308 EJ |
695 | |
696 | /* XXX: In the future allow users to configure the expected system ID. | |
697 | * For now just special case loopback. */ | |
91fc374a BP |
698 | if (eth_addr_equals(member->partner.sys_id, member->lacp->sys_id)) { |
699 | VLOG_WARN_RL(&rl, "member %s: Loopback detected. Interface is " | |
700 | "connected to its own bond", member->name); | |
6aa74308 EJ |
701 | continue; |
702 | } | |
703 | ||
91fc374a | 704 | if (member->status == LACP_DEFAULTED) { |
9dd165e0 | 705 | if (lacp->fallback_ab) { |
91fc374a | 706 | member->attached = true; |
9dd165e0 | 707 | } |
6aa74308 EJ |
708 | continue; |
709 | } | |
710 | ||
91fc374a BP |
711 | member_get_priority(member, &pri); |
712 | bool enable = member_may_enable__(member); | |
6aa74308 | 713 | |
6f50056c | 714 | /* Check if partner MAC address is the same as on the working |
91fc374a | 715 | * interface. Activate member only if the MAC is the same, or |
6f50056c RM |
716 | * there is no working interface. */ |
717 | if (!lead_current || (lead_current | |
91fc374a | 718 | && eth_addr_equals(member->partner.sys_id, |
6f50056c | 719 | lead_current->partner.sys_id))) { |
91fc374a | 720 | member->attached = true; |
6f50056c | 721 | } |
91fc374a | 722 | if (member->attached && |
6f50056c RM |
723 | (!lead |
724 | || enable > lead_enable | |
725 | || (enable == lead_enable | |
726 | && memcmp(&pri, &lead_pri, sizeof pri) < 0))) { | |
91fc374a | 727 | lead = member; |
86c8462d | 728 | lead_enable = enable; |
6aa74308 EJ |
729 | lead_pri = pri; |
730 | } | |
731 | } | |
732 | ||
733 | lacp->negotiated = lead != NULL; | |
734 | ||
735 | if (lead) { | |
91fc374a BP |
736 | HMAP_FOR_EACH (member, node, &lacp->members) { |
737 | if ((lacp->fallback_ab && member->status == LACP_DEFAULTED) | |
738 | || lead->partner.key != member->partner.key | |
6aa74308 | 739 | || !eth_addr_equals(lead->partner.sys_id, |
91fc374a BP |
740 | member->partner.sys_id)) { |
741 | member->attached = false; | |
6aa74308 EJ |
742 | } |
743 | } | |
744 | } | |
745 | } | |
746 | ||
747 | static void | |
91fc374a | 748 | member_destroy(struct member *member) OVS_REQUIRES(mutex) |
6aa74308 | 749 | { |
91fc374a BP |
750 | if (member) { |
751 | struct lacp *lacp = member->lacp; | |
6aa74308 EJ |
752 | |
753 | lacp->update = true; | |
91fc374a | 754 | hmap_remove(&lacp->members, &member->node); |
6aa74308 | 755 | |
91fc374a BP |
756 | if (lacp->key_member == member) { |
757 | struct hmap_node *member_node = hmap_first(&lacp->members); | |
6aa74308 | 758 | |
91fc374a BP |
759 | if (member_node) { |
760 | lacp->key_member = CONTAINER_OF(member_node, struct member, | |
761 | node); | |
6aa74308 | 762 | } else { |
91fc374a | 763 | lacp->key_member = NULL; |
6aa74308 EJ |
764 | } |
765 | } | |
766 | ||
91fc374a BP |
767 | free(member->name); |
768 | free(member); | |
6aa74308 EJ |
769 | } |
770 | } | |
771 | ||
772 | static void | |
91fc374a | 773 | member_set_defaulted(struct member *member) OVS_REQUIRES(mutex) |
6aa74308 | 774 | { |
91fc374a | 775 | memset(&member->partner, 0, sizeof member->partner); |
6aa74308 | 776 | |
91fc374a BP |
777 | member->lacp->update = true; |
778 | member->status = LACP_DEFAULTED; | |
6aa74308 EJ |
779 | } |
780 | ||
781 | static void | |
91fc374a | 782 | member_set_expired(struct member *member) OVS_REQUIRES(mutex) |
6aa74308 | 783 | { |
91fc374a BP |
784 | member->status = LACP_EXPIRED; |
785 | member->partner.state |= LACP_STATE_TIME; | |
786 | member->partner.state &= ~LACP_STATE_SYNC; | |
cdcf42c6 | 787 | |
91fc374a | 788 | timer_set_duration(&member->rx, LACP_RX_MULTIPLIER * LACP_FAST_TIME_TX); |
6aa74308 EJ |
789 | } |
790 | ||
791 | static void | |
91fc374a | 792 | member_get_actor(struct member *member, struct lacp_info *actor) |
bd3950dd | 793 | OVS_REQUIRES(mutex) |
6aa74308 | 794 | { |
91fc374a | 795 | struct lacp *lacp = member->lacp; |
e1ce3f2d | 796 | uint16_t key; |
6aa74308 EJ |
797 | uint8_t state = 0; |
798 | ||
da2f7b8f | 799 | if (lacp->active) { |
6aa74308 EJ |
800 | state |= LACP_STATE_ACT; |
801 | } | |
802 | ||
bf83f7c8 | 803 | if (lacp->fast) { |
269340fa EJ |
804 | state |= LACP_STATE_TIME; |
805 | } | |
806 | ||
91fc374a | 807 | if (member->attached) { |
6aa74308 EJ |
808 | state |= LACP_STATE_SYNC; |
809 | } | |
810 | ||
91fc374a | 811 | if (member->status == LACP_DEFAULTED) { |
6aa74308 EJ |
812 | state |= LACP_STATE_DEF; |
813 | } | |
814 | ||
91fc374a | 815 | if (member->status == LACP_EXPIRED) { |
6aa74308 EJ |
816 | state |= LACP_STATE_EXP; |
817 | } | |
818 | ||
91fc374a | 819 | if (hmap_count(&lacp->members) > 1) { |
6aa74308 EJ |
820 | state |= LACP_STATE_AGG; |
821 | } | |
822 | ||
91fc374a | 823 | if (member->attached || !lacp->negotiated) { |
6aa74308 EJ |
824 | state |= LACP_STATE_COL | LACP_STATE_DIST; |
825 | } | |
826 | ||
91fc374a | 827 | key = lacp->key_member->key; |
e1ce3f2d | 828 | if (!key) { |
91fc374a | 829 | key = lacp->key_member->port_id; |
e1ce3f2d EJ |
830 | } |
831 | ||
77fdfa9b | 832 | actor->state = state; |
e1ce3f2d | 833 | actor->key = htons(key); |
91fc374a BP |
834 | actor->port_priority = htons(member->port_priority); |
835 | actor->port_id = htons(member->port_id); | |
da2f7b8f | 836 | actor->sys_priority = htons(lacp->sys_priority); |
74ff3298 | 837 | actor->sys_id = lacp->sys_id; |
6aa74308 EJ |
838 | } |
839 | ||
91fc374a | 840 | /* Given 'member', populates 'priority' with data representing its LACP link |
6aa74308 EJ |
841 | * priority. If two priority objects populated by this function are compared |
842 | * using memcmp, the higher priority link will be less than the lower priority | |
843 | * link. */ | |
844 | static void | |
91fc374a | 845 | member_get_priority(struct member *member, struct lacp_info *priority) |
bd3950dd | 846 | OVS_REQUIRES(mutex) |
6aa74308 EJ |
847 | { |
848 | uint16_t partner_priority, actor_priority; | |
849 | ||
850 | /* Choose the lacp_info of the higher priority system by comparing their | |
851 | * system priorities and mac addresses. */ | |
91fc374a BP |
852 | actor_priority = member->lacp->sys_priority; |
853 | partner_priority = ntohs(member->partner.sys_priority); | |
6aa74308 | 854 | if (actor_priority < partner_priority) { |
91fc374a | 855 | member_get_actor(member, priority); |
6aa74308 | 856 | } else if (partner_priority < actor_priority) { |
91fc374a BP |
857 | *priority = member->partner; |
858 | } else if (eth_addr_compare_3way(member->lacp->sys_id, | |
859 | member->partner.sys_id) < 0) { | |
860 | member_get_actor(member, priority); | |
6aa74308 | 861 | } else { |
91fc374a | 862 | *priority = member->partner; |
6aa74308 EJ |
863 | } |
864 | ||
865 | /* Key and state are not used in priority comparisons. */ | |
866 | priority->key = 0; | |
867 | priority->state = 0; | |
868 | } | |
869 | ||
870 | static bool | |
91fc374a | 871 | member_may_tx(const struct member *member) OVS_REQUIRES(mutex) |
6aa74308 | 872 | { |
b3e8cd6b | 873 | /* Check for L1 state as well as LACP state. */ |
91fc374a BP |
874 | return (member->carrier_up) && ((member->lacp->active) || |
875 | (member->status != LACP_DEFAULTED)); | |
6aa74308 EJ |
876 | } |
877 | ||
91fc374a BP |
878 | static struct member * |
879 | member_lookup(const struct lacp *lacp, const void *member_) OVS_REQUIRES(mutex) | |
6aa74308 | 880 | { |
91fc374a | 881 | struct member *member; |
6aa74308 | 882 | |
91fc374a BP |
883 | HMAP_FOR_EACH_IN_BUCKET (member, node, hash_pointer(member_, 0), |
884 | &lacp->members) { | |
885 | if (member->aux == member_) { | |
886 | return member; | |
6aa74308 EJ |
887 | } |
888 | } | |
889 | ||
890 | return NULL; | |
891 | } | |
77fdfa9b EJ |
892 | |
893 | /* Two lacp_info structures are tx_equal if and only if they do not differ in | |
894 | * ways which would require a lacp_pdu transmission. */ | |
895 | static bool | |
896 | info_tx_equal(struct lacp_info *a, struct lacp_info *b) | |
897 | { | |
898 | ||
899 | /* LACP specification dictates that we transmit whenever the actor and | |
900 | * remote_actor differ in the following fields: Port, Port Priority, | |
901 | * System, System Priority, Aggregation Key, Activity State, Timeout State, | |
902 | * Sync State, and Aggregation State. The state flags are most likely to | |
903 | * change so are checked first. */ | |
904 | return !((a->state ^ b->state) & (LACP_STATE_ACT | |
905 | | LACP_STATE_TIME | |
906 | | LACP_STATE_SYNC | |
907 | | LACP_STATE_AGG)) | |
908 | && a->port_id == b->port_id | |
909 | && a->port_priority == b->port_priority | |
910 | && a->key == b->key | |
911 | && a->sys_priority == b->sys_priority | |
912 | && eth_addr_equals(a->sys_id, b->sys_id); | |
913 | } | |
6aa74308 EJ |
914 | \f |
915 | static struct lacp * | |
344e21d4 | 916 | lacp_find(const char *name) OVS_REQUIRES(mutex) |
6aa74308 EJ |
917 | { |
918 | struct lacp *lacp; | |
919 | ||
b468782a | 920 | LIST_FOR_EACH (lacp, node, all_lacps) { |
6aa74308 EJ |
921 | if (!strcmp(lacp->name, name)) { |
922 | return lacp; | |
923 | } | |
924 | } | |
925 | ||
926 | return NULL; | |
927 | } | |
928 | ||
929 | static void | |
930 | ds_put_lacp_state(struct ds *ds, uint8_t state) | |
931 | { | |
932 | if (state & LACP_STATE_ACT) { | |
1c1df7f1 | 933 | ds_put_cstr(ds, " activity"); |
6aa74308 EJ |
934 | } |
935 | ||
936 | if (state & LACP_STATE_TIME) { | |
1c1df7f1 | 937 | ds_put_cstr(ds, " timeout"); |
6aa74308 EJ |
938 | } |
939 | ||
940 | if (state & LACP_STATE_AGG) { | |
1c1df7f1 | 941 | ds_put_cstr(ds, " aggregation"); |
6aa74308 EJ |
942 | } |
943 | ||
944 | if (state & LACP_STATE_SYNC) { | |
1c1df7f1 | 945 | ds_put_cstr(ds, " synchronized"); |
6aa74308 EJ |
946 | } |
947 | ||
948 | if (state & LACP_STATE_COL) { | |
1c1df7f1 | 949 | ds_put_cstr(ds, " collecting"); |
6aa74308 EJ |
950 | } |
951 | ||
952 | if (state & LACP_STATE_DIST) { | |
1c1df7f1 | 953 | ds_put_cstr(ds, " distributing"); |
6aa74308 EJ |
954 | } |
955 | ||
956 | if (state & LACP_STATE_DEF) { | |
1c1df7f1 | 957 | ds_put_cstr(ds, " defaulted"); |
6aa74308 EJ |
958 | } |
959 | ||
960 | if (state & LACP_STATE_EXP) { | |
1c1df7f1 | 961 | ds_put_cstr(ds, " expired"); |
6aa74308 EJ |
962 | } |
963 | } | |
964 | ||
965 | static void | |
344e21d4 | 966 | lacp_print_details(struct ds *ds, struct lacp *lacp) OVS_REQUIRES(mutex) |
6aa74308 | 967 | { |
91fc374a BP |
968 | struct shash member_shash = SHASH_INITIALIZER(&member_shash); |
969 | const struct shash_node **sorted_members = NULL; | |
431d6a6a | 970 | |
91fc374a | 971 | struct member *member; |
431d6a6a | 972 | int i; |
6aa74308 | 973 | |
5dab8ece | 974 | ds_put_format(ds, "---- %s ----\n", lacp->name); |
6d7d467e | 975 | ds_put_format(ds, " status: %s", lacp->active ? "active" : "passive"); |
20601813 | 976 | if (lacp->negotiated) { |
5dab8ece | 977 | ds_put_cstr(ds, " negotiated"); |
20601813 | 978 | } |
5dab8ece | 979 | ds_put_cstr(ds, "\n"); |
20601813 | 980 | |
6d7d467e BP |
981 | ds_put_format(ds, " sys_id: " ETH_ADDR_FMT "\n", ETH_ADDR_ARGS(lacp->sys_id)); |
982 | ds_put_format(ds, " sys_priority: %u\n", lacp->sys_priority); | |
983 | ds_put_cstr(ds, " aggregation key: "); | |
91fc374a BP |
984 | if (lacp->key_member) { |
985 | ds_put_format(ds, "%u", lacp->key_member->key | |
986 | ? lacp->key_member->key | |
987 | : lacp->key_member->port_id); | |
6aa74308 | 988 | } else { |
5dab8ece | 989 | ds_put_cstr(ds, "none"); |
6aa74308 | 990 | } |
5dab8ece | 991 | ds_put_cstr(ds, "\n"); |
6aa74308 | 992 | |
6d7d467e | 993 | ds_put_cstr(ds, " lacp_time: "); |
bf83f7c8 | 994 | if (lacp->fast) { |
5dab8ece | 995 | ds_put_cstr(ds, "fast\n"); |
bf83f7c8 | 996 | } else { |
5dab8ece | 997 | ds_put_cstr(ds, "slow\n"); |
cdcf42c6 EJ |
998 | } |
999 | ||
91fc374a BP |
1000 | HMAP_FOR_EACH (member, node, &lacp->members) { |
1001 | shash_add(&member_shash, member->name, member); | |
431d6a6a | 1002 | } |
91fc374a | 1003 | sorted_members = shash_sort(&member_shash); |
431d6a6a | 1004 | |
91fc374a | 1005 | for (i = 0; i < shash_count(&member_shash); i++) { |
6aa74308 | 1006 | char *status; |
77fdfa9b | 1007 | struct lacp_info actor; |
6aa74308 | 1008 | |
91fc374a BP |
1009 | member = sorted_members[i]->data; |
1010 | member_get_actor(member, &actor); | |
1011 | switch (member->status) { | |
6aa74308 EJ |
1012 | case LACP_CURRENT: |
1013 | status = "current"; | |
1014 | break; | |
1015 | case LACP_EXPIRED: | |
1016 | status = "expired"; | |
1017 | break; | |
1018 | case LACP_DEFAULTED: | |
1019 | status = "defaulted"; | |
1020 | break; | |
1021 | default: | |
428b2edd | 1022 | OVS_NOT_REACHED(); |
6aa74308 EJ |
1023 | } |
1024 | ||
91fc374a BP |
1025 | ds_put_format(ds, "\nmember: %s: %s %s\n", member->name, status, |
1026 | member->attached ? "attached" : "detached"); | |
1027 | ds_put_format(ds, " port_id: %u\n", member->port_id); | |
1028 | ds_put_format(ds, " port_priority: %u\n", member->port_priority); | |
1029 | ds_put_format(ds, " may_enable: %s\n", (member_may_enable__(member) | |
29985e75 | 1030 | ? "true" : "false")); |
6aa74308 | 1031 | |
6d7d467e | 1032 | ds_put_format(ds, "\n actor sys_id: " ETH_ADDR_FMT "\n", |
77fdfa9b | 1033 | ETH_ADDR_ARGS(actor.sys_id)); |
6d7d467e | 1034 | ds_put_format(ds, " actor sys_priority: %u\n", |
77fdfa9b | 1035 | ntohs(actor.sys_priority)); |
6d7d467e | 1036 | ds_put_format(ds, " actor port_id: %u\n", |
77fdfa9b | 1037 | ntohs(actor.port_id)); |
6d7d467e | 1038 | ds_put_format(ds, " actor port_priority: %u\n", |
77fdfa9b | 1039 | ntohs(actor.port_priority)); |
6d7d467e | 1040 | ds_put_format(ds, " actor key: %u\n", |
77fdfa9b | 1041 | ntohs(actor.key)); |
6d7d467e | 1042 | ds_put_cstr(ds, " actor state:"); |
5dab8ece JP |
1043 | ds_put_lacp_state(ds, actor.state); |
1044 | ds_put_cstr(ds, "\n\n"); | |
6aa74308 | 1045 | |
6d7d467e | 1046 | ds_put_format(ds, " partner sys_id: " ETH_ADDR_FMT "\n", |
91fc374a | 1047 | ETH_ADDR_ARGS(member->partner.sys_id)); |
6d7d467e | 1048 | ds_put_format(ds, " partner sys_priority: %u\n", |
91fc374a | 1049 | ntohs(member->partner.sys_priority)); |
6d7d467e | 1050 | ds_put_format(ds, " partner port_id: %u\n", |
91fc374a | 1051 | ntohs(member->partner.port_id)); |
6d7d467e | 1052 | ds_put_format(ds, " partner port_priority: %u\n", |
91fc374a | 1053 | ntohs(member->partner.port_priority)); |
6d7d467e | 1054 | ds_put_format(ds, " partner key: %u\n", |
91fc374a | 1055 | ntohs(member->partner.key)); |
6d7d467e | 1056 | ds_put_cstr(ds, " partner state:"); |
91fc374a | 1057 | ds_put_lacp_state(ds, member->partner.state); |
5dab8ece JP |
1058 | ds_put_cstr(ds, "\n"); |
1059 | } | |
431d6a6a | 1060 | |
91fc374a BP |
1061 | shash_destroy(&member_shash); |
1062 | free(sorted_members); | |
5dab8ece JP |
1063 | } |
1064 | ||
49b9cad3 NK |
1065 | static void |
1066 | lacp_print_stats(struct ds *ds, struct lacp *lacp) OVS_REQUIRES(mutex) | |
1067 | { | |
91fc374a BP |
1068 | struct shash member_shash = SHASH_INITIALIZER(&member_shash); |
1069 | const struct shash_node **sorted_members = NULL; | |
49b9cad3 | 1070 | |
91fc374a | 1071 | struct member *member; |
49b9cad3 NK |
1072 | int i; |
1073 | ||
1074 | ds_put_format(ds, "---- %s statistics ----\n", lacp->name); | |
1075 | ||
91fc374a BP |
1076 | HMAP_FOR_EACH (member, node, &lacp->members) { |
1077 | shash_add(&member_shash, member->name, member); | |
49b9cad3 | 1078 | } |
91fc374a BP |
1079 | sorted_members = shash_sort(&member_shash); |
1080 | ||
1081 | for (i = 0; i < shash_count(&member_shash); i++) { | |
1082 | member = sorted_members[i]->data; | |
1083 | ds_put_format(ds, "\nmember: %s:\n", member->name); | |
1084 | ds_put_format(ds, " TX PDUs: %u\n", member->count_tx_pdus); | |
1085 | ds_put_format(ds, " RX PDUs: %u\n", member->count_rx_pdus); | |
1086 | ds_put_format(ds, " RX Bad PDUs: %u\n", member->count_rx_pdus_bad); | |
9248e103 | 1087 | ds_put_format(ds, " RX Marker Request PDUs: %u\n", |
91fc374a | 1088 | member->count_rx_pdus_marker); |
6d7d467e | 1089 | ds_put_format(ds, " Link Expired: %u\n", |
91fc374a | 1090 | member->count_link_expired); |
6d7d467e | 1091 | ds_put_format(ds, " Link Defaulted: %u\n", |
91fc374a | 1092 | member->count_link_defaulted); |
6d7d467e | 1093 | ds_put_format(ds, " Carrier Status Changed: %u\n", |
91fc374a | 1094 | member->count_carrier_changed); |
49b9cad3 NK |
1095 | } |
1096 | ||
91fc374a BP |
1097 | shash_destroy(&member_shash); |
1098 | free(sorted_members); | |
49b9cad3 NK |
1099 | } |
1100 | ||
5dab8ece | 1101 | static void |
0e15264f | 1102 | lacp_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], |
b468782a | 1103 | void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) |
5dab8ece JP |
1104 | { |
1105 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1106 | struct lacp *lacp; | |
1107 | ||
2339e869 | 1108 | lacp_lock(); |
0e15264f BP |
1109 | if (argc > 1) { |
1110 | lacp = lacp_find(argv[1]); | |
5dab8ece | 1111 | if (!lacp) { |
bde9f75d | 1112 | unixctl_command_reply_error(conn, "no such lacp object"); |
b468782a | 1113 | goto out; |
5dab8ece JP |
1114 | } |
1115 | lacp_print_details(&ds, lacp); | |
1116 | } else { | |
b468782a | 1117 | LIST_FOR_EACH (lacp, node, all_lacps) { |
5dab8ece JP |
1118 | lacp_print_details(&ds, lacp); |
1119 | } | |
6aa74308 EJ |
1120 | } |
1121 | ||
bde9f75d | 1122 | unixctl_command_reply(conn, ds_cstr(&ds)); |
6aa74308 | 1123 | ds_destroy(&ds); |
b468782a EJ |
1124 | |
1125 | out: | |
2339e869 | 1126 | lacp_unlock(); |
6aa74308 | 1127 | } |
50b9699f | 1128 | |
49b9cad3 NK |
1129 | static void |
1130 | lacp_unixctl_show_stats(struct unixctl_conn *conn, | |
1131 | int argc, | |
1132 | const char *argv[], | |
1133 | void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) | |
1134 | { | |
1135 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1136 | struct lacp *lacp; | |
1137 | ||
1138 | lacp_lock(); | |
1139 | if (argc > 1) { | |
1140 | lacp = lacp_find(argv[1]); | |
1141 | if (!lacp) { | |
1142 | unixctl_command_reply_error(conn, "no such lacp object"); | |
1143 | goto out; | |
1144 | } | |
1145 | lacp_print_stats(&ds, lacp); | |
1146 | } else { | |
1147 | LIST_FOR_EACH (lacp, node, all_lacps) { | |
1148 | lacp_print_stats(&ds, lacp); | |
1149 | } | |
1150 | } | |
1151 | ||
1152 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
1153 | ds_destroy(&ds); | |
1154 | ||
1155 | out: | |
1156 | lacp_unlock(); | |
1157 | } | |
1158 | ||
91fc374a BP |
1159 | /* Extract a snapshot of the current state and counters for a member port. |
1160 | Return false if the member is not active. */ | |
50b9699f | 1161 | bool |
91fc374a BP |
1162 | lacp_get_member_stats(const struct lacp *lacp, const void *member_, |
1163 | struct lacp_member_stats *stats) | |
50b9699f NM |
1164 | OVS_EXCLUDED(mutex) |
1165 | { | |
91fc374a | 1166 | struct member *member; |
50b9699f NM |
1167 | struct lacp_info actor; |
1168 | bool ret; | |
1169 | ||
1170 | ovs_mutex_lock(&mutex); | |
1171 | ||
91fc374a BP |
1172 | member = member_lookup(lacp, member_); |
1173 | if (member) { | |
6d7d467e | 1174 | ret = true; |
91fc374a | 1175 | member_get_actor(member, &actor); |
6d7d467e | 1176 | stats->dot3adAggPortActorSystemID = actor.sys_id; |
91fc374a BP |
1177 | stats->dot3adAggPortPartnerOperSystemID = member->partner.sys_id; |
1178 | stats->dot3adAggPortAttachedAggID = (lacp->key_member->key ? | |
1179 | lacp->key_member->key : | |
1180 | lacp->key_member->port_id); | |
6d7d467e BP |
1181 | |
1182 | /* Construct my admin-state. Assume aggregation is configured on. */ | |
1183 | stats->dot3adAggPortActorAdminState = LACP_STATE_AGG; | |
1184 | if (lacp->active) { | |
1185 | stats->dot3adAggPortActorAdminState |= LACP_STATE_ACT; | |
1186 | } | |
1187 | if (lacp->fast) { | |
1188 | stats->dot3adAggPortActorAdminState |= LACP_STATE_TIME; | |
1189 | } | |
1190 | /* XXX Not sure how to know the partner admin state. It | |
1191 | * might have to be captured and remembered during the | |
1192 | * negotiation phase. | |
1193 | */ | |
1194 | stats->dot3adAggPortPartnerAdminState = 0; | |
1195 | ||
1196 | stats->dot3adAggPortActorOperState = actor.state; | |
91fc374a | 1197 | stats->dot3adAggPortPartnerOperState = member->partner.state; |
6d7d467e BP |
1198 | |
1199 | /* Read out the latest counters */ | |
91fc374a BP |
1200 | stats->dot3adAggPortStatsLACPDUsRx = member->count_rx_pdus; |
1201 | stats->dot3adAggPortStatsIllegalRx = member->count_rx_pdus_bad; | |
1202 | stats->dot3adAggPortStatsLACPDUsTx = member->count_tx_pdus; | |
50b9699f NM |
1203 | } else { |
1204 | ret = false; | |
1205 | } | |
1206 | ovs_mutex_unlock(&mutex); | |
1207 | return ret; | |
1208 | ||
1209 | } |