]>
Commit | Line | Data |
---|---|---|
834d6caf | 1 | /* Copyright (c) 2011, 2012, 2013 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" |
6aa74308 EJ |
22 | #include "dynamic-string.h" |
23 | #include "hash.h" | |
24 | #include "hmap.h" | |
25 | #include "ofpbuf.h" | |
26 | #include "packets.h" | |
27 | #include "poll-loop.h" | |
f23d157c | 28 | #include "seq.h" |
431d6a6a | 29 | #include "shash.h" |
20834809 | 30 | #include "timer.h" |
6aa74308 EJ |
31 | #include "timeval.h" |
32 | #include "unixctl.h" | |
33 | #include "vlog.h" | |
34 | ||
35 | VLOG_DEFINE_THIS_MODULE(lacp); | |
36 | ||
5f877369 EJ |
37 | /* Masks for lacp_info state member. */ |
38 | #define LACP_STATE_ACT 0x01 /* Activity. Active or passive? */ | |
39 | #define LACP_STATE_TIME 0x02 /* Timeout. Short or long timeout? */ | |
40 | #define LACP_STATE_AGG 0x04 /* Aggregation. Is the link is bondable? */ | |
41 | #define LACP_STATE_SYNC 0x08 /* Synchronization. Is the link in up to date? */ | |
42 | #define LACP_STATE_COL 0x10 /* Collecting. Is the link receiving frames? */ | |
43 | #define LACP_STATE_DIST 0x20 /* Distributing. Is the link sending frames? */ | |
44 | #define LACP_STATE_DEF 0x40 /* Defaulted. Using default partner info? */ | |
45 | #define LACP_STATE_EXP 0x80 /* Expired. Using expired partner info? */ | |
46 | ||
47 | #define LACP_FAST_TIME_TX 1000 /* Fast transmission rate. */ | |
48 | #define LACP_SLOW_TIME_TX 30000 /* Slow transmission rate. */ | |
49 | #define LACP_RX_MULTIPLIER 3 /* Multiply by TX rate to get RX rate. */ | |
50 | ||
51 | #define LACP_INFO_LEN 15 | |
13b6bae6 | 52 | OVS_PACKED( |
5f877369 EJ |
53 | struct lacp_info { |
54 | ovs_be16 sys_priority; /* System priority. */ | |
55 | uint8_t sys_id[ETH_ADDR_LEN]; /* System ID. */ | |
56 | ovs_be16 key; /* Operational key. */ | |
57 | ovs_be16 port_priority; /* Port priority. */ | |
58 | ovs_be16 port_id; /* Port ID. */ | |
59 | uint8_t state; /* State mask. See LACP_STATE macros. */ | |
13b6bae6 | 60 | }); |
5f877369 EJ |
61 | BUILD_ASSERT_DECL(LACP_INFO_LEN == sizeof(struct lacp_info)); |
62 | ||
63 | #define LACP_PDU_LEN 110 | |
13b6bae6 | 64 | OVS_PACKED( |
5f877369 EJ |
65 | struct lacp_pdu { |
66 | uint8_t subtype; /* Always 1. */ | |
67 | uint8_t version; /* Always 1. */ | |
68 | ||
69 | uint8_t actor_type; /* Always 1. */ | |
70 | uint8_t actor_len; /* Always 20. */ | |
71 | struct lacp_info actor; /* LACP actor information. */ | |
72 | uint8_t z1[3]; /* Reserved. Always 0. */ | |
73 | ||
74 | uint8_t partner_type; /* Always 2. */ | |
75 | uint8_t partner_len; /* Always 20. */ | |
76 | struct lacp_info partner; /* LACP partner information. */ | |
77 | uint8_t z2[3]; /* Reserved. Always 0. */ | |
78 | ||
79 | uint8_t collector_type; /* Always 3. */ | |
80 | uint8_t collector_len; /* Always 16. */ | |
81 | ovs_be16 collector_delay; /* Maximum collector delay. Set to UINT16_MAX. */ | |
82 | uint8_t z3[64]; /* Combination of several fields. Always 0. */ | |
13b6bae6 | 83 | }); |
5f877369 EJ |
84 | BUILD_ASSERT_DECL(LACP_PDU_LEN == sizeof(struct lacp_pdu)); |
85 | \f | |
86 | /* Implementation. */ | |
87 | ||
6aa74308 EJ |
88 | enum slave_status { |
89 | LACP_CURRENT, /* Current State. Partner up to date. */ | |
90 | LACP_EXPIRED, /* Expired State. Partner out of date. */ | |
91 | LACP_DEFAULTED, /* Defaulted State. No partner. */ | |
92 | }; | |
93 | ||
94 | struct lacp { | |
95 | struct list node; /* Node in all_lacps list. */ | |
96 | char *name; /* Name of this lacp object. */ | |
97 | uint8_t sys_id[ETH_ADDR_LEN]; /* System ID. */ | |
98 | uint16_t sys_priority; /* System Priority. */ | |
99 | bool active; /* Active or Passive. */ | |
100 | ||
101 | struct hmap slaves; /* Slaves this LACP object controls. */ | |
102 | struct slave *key_slave; /* Slave whose ID will be the aggregation key. */ | |
103 | ||
bf83f7c8 | 104 | bool fast; /* True if using fast probe interval. */ |
6aa74308 EJ |
105 | bool negotiated; /* True if LACP negotiations were successful. */ |
106 | bool update; /* True if lacp_update() needs to be called. */ | |
9dd165e0 | 107 | bool fallback_ab; /* True if fallback to active-backup on LACP failure. */ |
91779071 | 108 | |
b468782a | 109 | atomic_int ref_cnt; |
6aa74308 EJ |
110 | }; |
111 | ||
112 | struct slave { | |
113 | void *aux; /* Handle used to identify this slave. */ | |
114 | struct hmap_node node; /* Node in master's slaves map. */ | |
115 | ||
116 | struct lacp *lacp; /* LACP object containing this slave. */ | |
117 | uint16_t port_id; /* Port ID. */ | |
118 | uint16_t port_priority; /* Port Priority. */ | |
e1ce3f2d | 119 | uint16_t key; /* Aggregation Key. 0 if default. */ |
6aa74308 EJ |
120 | char *name; /* Name of this slave. */ |
121 | ||
122 | enum slave_status status; /* Slave status. */ | |
123 | bool attached; /* Attached. Traffic may flow. */ | |
6aa74308 | 124 | struct lacp_info partner; /* Partner information. */ |
77fdfa9b | 125 | struct lacp_info ntt_actor; /* Used to decide if we Need To Transmit. */ |
20834809 EJ |
126 | struct timer tx; /* Next message transmission timer. */ |
127 | struct timer rx; /* Expected message receive timer. */ | |
6aa74308 EJ |
128 | }; |
129 | ||
b468782a EJ |
130 | static struct ovs_mutex mutex; |
131 | static struct list all_lacps__ = LIST_INITIALIZER(&all_lacps__); | |
132 | static struct list *const all_lacps OVS_GUARDED_BY(mutex) = &all_lacps__; | |
133 | ||
bd3950dd | 134 | static void lacp_update_attached(struct lacp *) OVS_REQUIRES(mutex); |
b468782a | 135 | |
bd3950dd AW |
136 | static void slave_destroy(struct slave *) OVS_REQUIRES(mutex); |
137 | static void slave_set_defaulted(struct slave *) OVS_REQUIRES(mutex); | |
138 | static void slave_set_expired(struct slave *) OVS_REQUIRES(mutex); | |
b468782a | 139 | static void slave_get_actor(struct slave *, struct lacp_info *actor) |
bd3950dd | 140 | OVS_REQUIRES(mutex); |
b468782a | 141 | static void slave_get_priority(struct slave *, struct lacp_info *priority) |
bd3950dd | 142 | OVS_REQUIRES(mutex); |
b468782a | 143 | static bool slave_may_tx(const struct slave *) |
bd3950dd | 144 | OVS_REQUIRES(mutex); |
b468782a | 145 | static struct slave *slave_lookup(const struct lacp *, const void *slave) |
bd3950dd | 146 | OVS_REQUIRES(mutex); |
b468782a | 147 | static bool info_tx_equal(struct lacp_info *, struct lacp_info *) |
bd3950dd | 148 | OVS_REQUIRES(mutex); |
6aa74308 | 149 | |
0e15264f | 150 | static unixctl_cb_func lacp_unixctl_show; |
6aa74308 | 151 | |
81aee5f9 | 152 | /* Populates 'pdu' with a LACP PDU comprised of 'actor' and 'partner'. */ |
5f877369 | 153 | static void |
81aee5f9 EJ |
154 | compose_lacp_pdu(const struct lacp_info *actor, |
155 | const struct lacp_info *partner, struct lacp_pdu *pdu) | |
156 | { | |
157 | memset(pdu, 0, sizeof *pdu); | |
158 | ||
159 | pdu->subtype = 1; | |
160 | pdu->version = 1; | |
161 | ||
162 | pdu->actor_type = 1; | |
163 | pdu->actor_len = 20; | |
164 | pdu->actor = *actor; | |
165 | ||
166 | pdu->partner_type = 2; | |
167 | pdu->partner_len = 20; | |
168 | pdu->partner = *partner; | |
169 | ||
170 | pdu->collector_type = 3; | |
171 | pdu->collector_len = 16; | |
172 | pdu->collector_delay = htons(0); | |
173 | } | |
174 | ||
175 | /* Parses 'b' which represents a packet containing a LACP PDU. This function | |
176 | * returns NULL if 'b' is malformed, or does not represent a LACP PDU format | |
177 | * supported by OVS. Otherwise, it returns a pointer to the lacp_pdu contained | |
178 | * within 'b'. */ | |
5f877369 | 179 | static const struct lacp_pdu * |
81aee5f9 EJ |
180 | parse_lacp_packet(const struct ofpbuf *b) |
181 | { | |
182 | const struct lacp_pdu *pdu; | |
183 | ||
184 | pdu = ofpbuf_at(b, (uint8_t *)b->l3 - (uint8_t *)b->data, LACP_PDU_LEN); | |
185 | ||
186 | if (pdu && pdu->subtype == 1 | |
187 | && pdu->actor_type == 1 && pdu->actor_len == 20 | |
188 | && pdu->partner_type == 2 && pdu->partner_len == 20) { | |
189 | return pdu; | |
190 | } else { | |
191 | return NULL; | |
192 | } | |
193 | } | |
194 | \f | |
195 | /* LACP Protocol Implementation. */ | |
196 | ||
6aa74308 EJ |
197 | /* Initializes the lacp module. */ |
198 | void | |
199 | lacp_init(void) | |
200 | { | |
0e15264f BP |
201 | unixctl_command_register("lacp/show", "[port]", 0, 1, |
202 | lacp_unixctl_show, NULL); | |
6aa74308 EJ |
203 | } |
204 | ||
205 | /* Creates a LACP object. */ | |
206 | struct lacp * | |
b468782a | 207 | lacp_create(void) OVS_EXCLUDED(mutex) |
6aa74308 | 208 | { |
b468782a | 209 | static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; |
6aa74308 EJ |
210 | struct lacp *lacp; |
211 | ||
b468782a | 212 | if (ovsthread_once_start(&once)) { |
834d6caf | 213 | ovs_mutex_init_recursive(&mutex); |
b468782a EJ |
214 | ovsthread_once_done(&once); |
215 | } | |
216 | ||
6aa74308 EJ |
217 | lacp = xzalloc(sizeof *lacp); |
218 | hmap_init(&lacp->slaves); | |
b468782a EJ |
219 | atomic_init(&lacp->ref_cnt, 1); |
220 | ||
221 | ovs_mutex_lock(&mutex); | |
222 | list_push_back(all_lacps, &lacp->node); | |
223 | ovs_mutex_unlock(&mutex); | |
91779071 EJ |
224 | return lacp; |
225 | } | |
226 | ||
227 | struct lacp * | |
228 | lacp_ref(const struct lacp *lacp_) | |
229 | { | |
230 | struct lacp *lacp = CONST_CAST(struct lacp *, lacp_); | |
231 | if (lacp) { | |
b468782a EJ |
232 | int orig; |
233 | atomic_add(&lacp->ref_cnt, 1, &orig); | |
234 | ovs_assert(orig > 0); | |
91779071 | 235 | } |
6aa74308 EJ |
236 | return lacp; |
237 | } | |
238 | ||
239 | /* Destroys 'lacp' and its slaves. Does nothing if 'lacp' is NULL. */ | |
240 | void | |
b468782a | 241 | lacp_unref(struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 | 242 | { |
b468782a EJ |
243 | int orig; |
244 | ||
91779071 EJ |
245 | if (!lacp) { |
246 | return; | |
247 | } | |
248 | ||
b468782a EJ |
249 | atomic_sub(&lacp->ref_cnt, 1, &orig); |
250 | ovs_assert(orig > 0); | |
251 | if (orig == 1) { | |
6aa74308 EJ |
252 | struct slave *slave, *next; |
253 | ||
b468782a | 254 | ovs_mutex_lock(&mutex); |
6aa74308 EJ |
255 | HMAP_FOR_EACH_SAFE (slave, next, node, &lacp->slaves) { |
256 | slave_destroy(slave); | |
257 | } | |
258 | ||
259 | hmap_destroy(&lacp->slaves); | |
260 | list_remove(&lacp->node); | |
261 | free(lacp->name); | |
262 | free(lacp); | |
b468782a | 263 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
264 | } |
265 | } | |
266 | ||
bb5bc6c0 | 267 | /* Configures 'lacp' with settings from 's'. */ |
6aa74308 | 268 | void |
bb5bc6c0 | 269 | lacp_configure(struct lacp *lacp, const struct lacp_settings *s) |
b468782a | 270 | OVS_EXCLUDED(mutex) |
6aa74308 | 271 | { |
cb22974d | 272 | ovs_assert(!eth_addr_is_zero(s->id)); |
69fdadb1 | 273 | |
b468782a | 274 | ovs_mutex_lock(&mutex); |
bb5bc6c0 | 275 | if (!lacp->name || strcmp(s->name, lacp->name)) { |
6aa74308 | 276 | free(lacp->name); |
bb5bc6c0 | 277 | lacp->name = xstrdup(s->name); |
6aa74308 EJ |
278 | } |
279 | ||
eb458893 | 280 | if (!eth_addr_equals(lacp->sys_id, s->id) |
b20a8f7c | 281 | || lacp->sys_priority != s->priority) { |
eb458893 EJ |
282 | memcpy(lacp->sys_id, s->id, ETH_ADDR_LEN); |
283 | lacp->sys_priority = s->priority; | |
284 | lacp->update = true; | |
285 | } | |
286 | ||
bb5bc6c0 | 287 | lacp->active = s->active; |
bf83f7c8 | 288 | lacp->fast = s->fast; |
9dd165e0 RK |
289 | |
290 | if (lacp->fallback_ab != s->fallback_ab_cfg) { | |
291 | lacp->fallback_ab = s->fallback_ab_cfg; | |
292 | lacp->update = true; | |
293 | } | |
294 | ||
b468782a | 295 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
296 | } |
297 | ||
7a673515 BP |
298 | /* Returns true if 'lacp' is configured in active mode, false if 'lacp' is |
299 | * configured for passive mode. */ | |
300 | bool | |
b468782a | 301 | lacp_is_active(const struct lacp *lacp) OVS_EXCLUDED(mutex) |
7a673515 | 302 | { |
b468782a EJ |
303 | bool ret; |
304 | ovs_mutex_lock(&mutex); | |
305 | ret = lacp->active; | |
306 | ovs_mutex_unlock(&mutex); | |
307 | return ret; | |
7a673515 BP |
308 | } |
309 | ||
5f877369 EJ |
310 | /* Processes 'packet' which was received on 'slave_'. This function should be |
311 | * called on all packets received on 'slave_' with Ethernet Type ETH_TYPE_LACP. | |
312 | */ | |
6aa74308 | 313 | void |
5f877369 EJ |
314 | lacp_process_packet(struct lacp *lacp, const void *slave_, |
315 | const struct ofpbuf *packet) | |
b468782a | 316 | OVS_EXCLUDED(mutex) |
6aa74308 | 317 | { |
5f877369 | 318 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 5); |
5f877369 | 319 | const struct lacp_pdu *pdu; |
cdcf42c6 | 320 | long long int tx_rate; |
b468782a | 321 | struct slave *slave; |
cdcf42c6 | 322 | |
b468782a EJ |
323 | ovs_mutex_lock(&mutex); |
324 | slave = slave_lookup(lacp, slave_); | |
f1ce3514 | 325 | if (!slave) { |
b468782a | 326 | goto out; |
f1ce3514 EJ |
327 | } |
328 | ||
5f877369 EJ |
329 | pdu = parse_lacp_packet(packet); |
330 | if (!pdu) { | |
331 | VLOG_WARN_RL(&rl, "%s: received an unparsable LACP PDU.", lacp->name); | |
b468782a | 332 | goto out; |
5f877369 EJ |
333 | } |
334 | ||
6aa74308 | 335 | slave->status = LACP_CURRENT; |
bf83f7c8 | 336 | tx_rate = lacp->fast ? LACP_FAST_TIME_TX : LACP_SLOW_TIME_TX; |
cdcf42c6 | 337 | timer_set_duration(&slave->rx, LACP_RX_MULTIPLIER * tx_rate); |
6aa74308 | 338 | |
77fdfa9b | 339 | slave->ntt_actor = pdu->partner; |
6aa74308 EJ |
340 | |
341 | /* Update our information about our partner if it's out of date. This may | |
342 | * cause priorities to change so re-calculate attached status of all | |
343 | * slaves. */ | |
344 | if (memcmp(&slave->partner, &pdu->actor, sizeof pdu->actor)) { | |
345 | lacp->update = true; | |
346 | slave->partner = pdu->actor; | |
347 | } | |
b468782a EJ |
348 | |
349 | out: | |
350 | ovs_mutex_unlock(&mutex); | |
6aa74308 EJ |
351 | } |
352 | ||
bdebeece EJ |
353 | /* Returns the lacp_status of the given 'lacp' object (which may be NULL). */ |
354 | enum lacp_status | |
b468782a | 355 | lacp_status(const struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 | 356 | { |
b468782a EJ |
357 | enum lacp_status ret; |
358 | ||
359 | ovs_mutex_lock(&mutex); | |
bdebeece | 360 | if (!lacp) { |
b468782a | 361 | ret = LACP_DISABLED; |
bdebeece | 362 | } else if (lacp->negotiated) { |
b468782a | 363 | ret = LACP_NEGOTIATED; |
bdebeece | 364 | } else { |
b468782a | 365 | ret = LACP_CONFIGURED; |
bdebeece | 366 | } |
b468782a EJ |
367 | ovs_mutex_unlock(&mutex); |
368 | return ret; | |
6aa74308 EJ |
369 | } |
370 | ||
371 | /* Registers 'slave_' as subordinate to 'lacp'. This should be called at least | |
372 | * once per slave in a LACP managed bond. Should also be called whenever a | |
bb5bc6c0 | 373 | * slave's settings change. */ |
6aa74308 | 374 | void |
bb5bc6c0 BP |
375 | lacp_slave_register(struct lacp *lacp, void *slave_, |
376 | const struct lacp_slave_settings *s) | |
b468782a | 377 | OVS_EXCLUDED(mutex) |
6aa74308 | 378 | { |
b468782a | 379 | struct slave *slave; |
6aa74308 | 380 | |
b468782a EJ |
381 | ovs_mutex_lock(&mutex); |
382 | slave = slave_lookup(lacp, slave_); | |
6aa74308 EJ |
383 | if (!slave) { |
384 | slave = xzalloc(sizeof *slave); | |
385 | slave->lacp = lacp; | |
386 | slave->aux = slave_; | |
387 | hmap_insert(&lacp->slaves, &slave->node, hash_pointer(slave_, 0)); | |
388 | slave_set_defaulted(slave); | |
389 | ||
390 | if (!lacp->key_slave) { | |
391 | lacp->key_slave = slave; | |
392 | } | |
393 | } | |
394 | ||
bb5bc6c0 | 395 | if (!slave->name || strcmp(s->name, slave->name)) { |
6aa74308 | 396 | free(slave->name); |
bb5bc6c0 | 397 | slave->name = xstrdup(s->name); |
6aa74308 EJ |
398 | } |
399 | ||
e1ce3f2d EJ |
400 | if (slave->port_id != s->id |
401 | || slave->port_priority != s->priority | |
402 | || slave->key != s->key) { | |
bb5bc6c0 BP |
403 | slave->port_id = s->id; |
404 | slave->port_priority = s->priority; | |
e1ce3f2d | 405 | slave->key = s->key; |
6aa74308 | 406 | |
6aa74308 EJ |
407 | lacp->update = true; |
408 | ||
409 | if (lacp->active || lacp->negotiated) { | |
410 | slave_set_expired(slave); | |
411 | } | |
412 | } | |
b468782a | 413 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
414 | } |
415 | ||
416 | /* Unregisters 'slave_' with 'lacp'. */ | |
417 | void | |
418 | lacp_slave_unregister(struct lacp *lacp, const void *slave_) | |
b468782a | 419 | OVS_EXCLUDED(mutex) |
6aa74308 | 420 | { |
b468782a | 421 | struct slave *slave; |
6aa74308 | 422 | |
b468782a EJ |
423 | ovs_mutex_lock(&mutex); |
424 | slave = slave_lookup(lacp, slave_); | |
6aa74308 EJ |
425 | if (slave) { |
426 | slave_destroy(slave); | |
eb458893 | 427 | lacp->update = true; |
6aa74308 | 428 | } |
b468782a | 429 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
430 | } |
431 | ||
6aa74308 | 432 | /* This function should be called whenever the carrier status of 'slave_' has |
3e5b3fdb | 433 | * changed. If 'lacp' is null, this function has no effect.*/ |
6aa74308 EJ |
434 | void |
435 | lacp_slave_carrier_changed(const struct lacp *lacp, const void *slave_) | |
b468782a | 436 | OVS_EXCLUDED(mutex) |
6aa74308 | 437 | { |
b468782a EJ |
438 | struct slave *slave; |
439 | if (!lacp) { | |
440 | return; | |
441 | } | |
6aa74308 | 442 | |
b468782a EJ |
443 | ovs_mutex_lock(&mutex); |
444 | slave = slave_lookup(lacp, slave_); | |
445 | if (!slave) { | |
446 | goto out; | |
447 | } | |
f1ce3514 | 448 | |
b468782a EJ |
449 | if (slave->status == LACP_CURRENT || slave->lacp->active) { |
450 | slave_set_expired(slave); | |
6aa74308 | 451 | } |
b468782a EJ |
452 | |
453 | out: | |
454 | ovs_mutex_unlock(&mutex); | |
6aa74308 EJ |
455 | } |
456 | ||
29985e75 | 457 | static bool |
bd3950dd | 458 | slave_may_enable__(struct slave *slave) OVS_REQUIRES(mutex) |
29985e75 EJ |
459 | { |
460 | /* The slave may be enabled if it's attached to an aggregator and its | |
461 | * partner is synchronized.*/ | |
9dd165e0 RK |
462 | return slave->attached && (slave->partner.state & LACP_STATE_SYNC |
463 | || (slave->lacp && slave->lacp->fallback_ab | |
464 | && slave->status == LACP_DEFAULTED)); | |
29985e75 EJ |
465 | } |
466 | ||
6aa74308 EJ |
467 | /* This function should be called before enabling 'slave_' to send or receive |
468 | * traffic. If it returns false, 'slave_' should not enabled. As a | |
469 | * convenience, returns true if 'lacp' is NULL. */ | |
470 | bool | |
471 | lacp_slave_may_enable(const struct lacp *lacp, const void *slave_) | |
b468782a | 472 | OVS_EXCLUDED(mutex) |
6aa74308 EJ |
473 | { |
474 | if (lacp) { | |
b468782a EJ |
475 | struct slave *slave; |
476 | bool ret; | |
477 | ||
478 | ovs_mutex_lock(&mutex); | |
479 | slave = slave_lookup(lacp, slave_); | |
480 | ret = slave ? slave_may_enable__(slave) : false; | |
481 | ovs_mutex_unlock(&mutex); | |
482 | return ret; | |
6aa74308 EJ |
483 | } else { |
484 | return true; | |
485 | } | |
486 | } | |
487 | ||
44cb163f EJ |
488 | /* Returns true if partner information on 'slave_' is up to date. 'slave_' |
489 | * not being current, generally indicates a connectivity problem, or a | |
490 | * misconfigured (or broken) partner. */ | |
491 | bool | |
492 | lacp_slave_is_current(const struct lacp *lacp, const void *slave_) | |
b468782a | 493 | OVS_EXCLUDED(mutex) |
44cb163f | 494 | { |
b468782a EJ |
495 | struct slave *slave; |
496 | bool ret; | |
497 | ||
498 | ovs_mutex_lock(&mutex); | |
499 | slave = slave_lookup(lacp, slave_); | |
500 | ret = slave ? slave->status != LACP_DEFAULTED : false; | |
501 | ovs_mutex_unlock(&mutex); | |
502 | return ret; | |
44cb163f EJ |
503 | } |
504 | ||
6aa74308 EJ |
505 | /* This function should be called periodically to update 'lacp'. */ |
506 | void | |
b468782a | 507 | lacp_run(struct lacp *lacp, lacp_send_pdu *send_pdu) OVS_EXCLUDED(mutex) |
6aa74308 EJ |
508 | { |
509 | struct slave *slave; | |
510 | ||
b468782a | 511 | ovs_mutex_lock(&mutex); |
6aa74308 | 512 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { |
20834809 | 513 | if (timer_expired(&slave->rx)) { |
f23d157c JS |
514 | enum slave_status old_status = slave->status; |
515 | ||
6aa74308 EJ |
516 | if (slave->status == LACP_CURRENT) { |
517 | slave_set_expired(slave); | |
518 | } else if (slave->status == LACP_EXPIRED) { | |
519 | slave_set_defaulted(slave); | |
520 | } | |
f23d157c JS |
521 | if (slave->status != old_status) { |
522 | seq_change(connectivity_seq_get()); | |
523 | } | |
6aa74308 | 524 | } |
6aa74308 EJ |
525 | } |
526 | ||
527 | if (lacp->update) { | |
528 | lacp_update_attached(lacp); | |
529 | } | |
530 | ||
531 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { | |
77fdfa9b | 532 | struct lacp_info actor; |
6aa74308 | 533 | |
77fdfa9b | 534 | if (!slave_may_tx(slave)) { |
6aa74308 EJ |
535 | continue; |
536 | } | |
537 | ||
77fdfa9b EJ |
538 | slave_get_actor(slave, &actor); |
539 | ||
20834809 | 540 | if (timer_expired(&slave->tx) |
77fdfa9b | 541 | || !info_tx_equal(&actor, &slave->ntt_actor)) { |
cdcf42c6 | 542 | long long int duration; |
5f877369 | 543 | struct lacp_pdu pdu; |
77fdfa9b EJ |
544 | |
545 | slave->ntt_actor = actor; | |
546 | compose_lacp_pdu(&actor, &slave->partner, &pdu); | |
5f877369 | 547 | send_pdu(slave->aux, &pdu, sizeof pdu); |
6aa74308 | 548 | |
bf83f7c8 EJ |
549 | duration = (slave->partner.state & LACP_STATE_TIME |
550 | ? LACP_FAST_TIME_TX | |
551 | : LACP_SLOW_TIME_TX); | |
cdcf42c6 EJ |
552 | |
553 | timer_set_duration(&slave->tx, duration); | |
f23d157c | 554 | seq_change(connectivity_seq_get()); |
77fdfa9b | 555 | } |
6aa74308 | 556 | } |
b468782a | 557 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
558 | } |
559 | ||
560 | /* Causes poll_block() to wake up when lacp_run() needs to be called again. */ | |
561 | void | |
b468782a | 562 | lacp_wait(struct lacp *lacp) OVS_EXCLUDED(mutex) |
6aa74308 EJ |
563 | { |
564 | struct slave *slave; | |
565 | ||
b468782a | 566 | ovs_mutex_lock(&mutex); |
6aa74308 EJ |
567 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { |
568 | if (slave_may_tx(slave)) { | |
20834809 | 569 | timer_wait(&slave->tx); |
6aa74308 EJ |
570 | } |
571 | ||
572 | if (slave->status != LACP_DEFAULTED) { | |
20834809 | 573 | timer_wait(&slave->rx); |
6aa74308 EJ |
574 | } |
575 | } | |
b468782a | 576 | ovs_mutex_unlock(&mutex); |
6aa74308 EJ |
577 | } |
578 | \f | |
579 | /* Static Helpers. */ | |
580 | ||
20601813 | 581 | /* Updates the attached status of all slaves controlled by 'lacp' and sets its |
6aa74308 EJ |
582 | * negotiated parameter to true if any slaves are attachable. */ |
583 | static void | |
bd3950dd | 584 | lacp_update_attached(struct lacp *lacp) OVS_REQUIRES(mutex) |
6aa74308 EJ |
585 | { |
586 | struct slave *lead, *slave; | |
587 | struct lacp_info lead_pri; | |
588 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(1, 10); | |
589 | ||
590 | lacp->update = false; | |
591 | ||
592 | lead = NULL; | |
593 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { | |
594 | struct lacp_info pri; | |
595 | ||
bdebeece | 596 | slave->attached = false; |
6aa74308 EJ |
597 | |
598 | /* XXX: In the future allow users to configure the expected system ID. | |
599 | * For now just special case loopback. */ | |
77fdfa9b | 600 | if (eth_addr_equals(slave->partner.sys_id, slave->lacp->sys_id)) { |
6aa74308 EJ |
601 | VLOG_WARN_RL(&rl, "slave %s: Loopback detected. Slave is " |
602 | "connected to its own bond", slave->name); | |
6aa74308 EJ |
603 | continue; |
604 | } | |
605 | ||
606 | if (slave->status == LACP_DEFAULTED) { | |
9dd165e0 RK |
607 | if (lacp->fallback_ab) { |
608 | slave->attached = true; | |
609 | } | |
6aa74308 EJ |
610 | continue; |
611 | } | |
612 | ||
bdebeece | 613 | slave->attached = true; |
6aa74308 EJ |
614 | slave_get_priority(slave, &pri); |
615 | ||
616 | if (!lead || memcmp(&pri, &lead_pri, sizeof pri) < 0) { | |
617 | lead = slave; | |
618 | lead_pri = pri; | |
619 | } | |
620 | } | |
621 | ||
622 | lacp->negotiated = lead != NULL; | |
623 | ||
624 | if (lead) { | |
625 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { | |
9dd165e0 RK |
626 | if ((lacp->fallback_ab && slave->status == LACP_DEFAULTED) |
627 | || lead->partner.key != slave->partner.key | |
6aa74308 EJ |
628 | || !eth_addr_equals(lead->partner.sys_id, |
629 | slave->partner.sys_id)) { | |
630 | slave->attached = false; | |
631 | } | |
632 | } | |
633 | } | |
634 | } | |
635 | ||
636 | static void | |
bd3950dd | 637 | slave_destroy(struct slave *slave) OVS_REQUIRES(mutex) |
6aa74308 EJ |
638 | { |
639 | if (slave) { | |
640 | struct lacp *lacp = slave->lacp; | |
641 | ||
642 | lacp->update = true; | |
643 | hmap_remove(&lacp->slaves, &slave->node); | |
644 | ||
645 | if (lacp->key_slave == slave) { | |
646 | struct hmap_node *slave_node = hmap_first(&lacp->slaves); | |
647 | ||
648 | if (slave_node) { | |
649 | lacp->key_slave = CONTAINER_OF(slave_node, struct slave, node); | |
650 | } else { | |
651 | lacp->key_slave = NULL; | |
652 | } | |
653 | } | |
654 | ||
655 | free(slave->name); | |
656 | free(slave); | |
657 | } | |
658 | } | |
659 | ||
660 | static void | |
bd3950dd | 661 | slave_set_defaulted(struct slave *slave) OVS_REQUIRES(mutex) |
6aa74308 EJ |
662 | { |
663 | memset(&slave->partner, 0, sizeof slave->partner); | |
664 | ||
6aa74308 EJ |
665 | slave->lacp->update = true; |
666 | slave->status = LACP_DEFAULTED; | |
667 | } | |
668 | ||
669 | static void | |
bd3950dd | 670 | slave_set_expired(struct slave *slave) OVS_REQUIRES(mutex) |
6aa74308 EJ |
671 | { |
672 | slave->status = LACP_EXPIRED; | |
673 | slave->partner.state |= LACP_STATE_TIME; | |
674 | slave->partner.state &= ~LACP_STATE_SYNC; | |
cdcf42c6 | 675 | |
bf83f7c8 | 676 | timer_set_duration(&slave->rx, LACP_RX_MULTIPLIER * LACP_FAST_TIME_TX); |
6aa74308 EJ |
677 | } |
678 | ||
679 | static void | |
77fdfa9b | 680 | slave_get_actor(struct slave *slave, struct lacp_info *actor) |
bd3950dd | 681 | OVS_REQUIRES(mutex) |
6aa74308 | 682 | { |
da2f7b8f | 683 | struct lacp *lacp = slave->lacp; |
e1ce3f2d | 684 | uint16_t key; |
6aa74308 EJ |
685 | uint8_t state = 0; |
686 | ||
da2f7b8f | 687 | if (lacp->active) { |
6aa74308 EJ |
688 | state |= LACP_STATE_ACT; |
689 | } | |
690 | ||
bf83f7c8 | 691 | if (lacp->fast) { |
269340fa EJ |
692 | state |= LACP_STATE_TIME; |
693 | } | |
694 | ||
6aa74308 EJ |
695 | if (slave->attached) { |
696 | state |= LACP_STATE_SYNC; | |
697 | } | |
698 | ||
699 | if (slave->status == LACP_DEFAULTED) { | |
700 | state |= LACP_STATE_DEF; | |
701 | } | |
702 | ||
703 | if (slave->status == LACP_EXPIRED) { | |
704 | state |= LACP_STATE_EXP; | |
705 | } | |
706 | ||
b20a8f7c | 707 | if (hmap_count(&lacp->slaves) > 1) { |
6aa74308 EJ |
708 | state |= LACP_STATE_AGG; |
709 | } | |
710 | ||
da2f7b8f | 711 | if (slave->attached || !lacp->negotiated) { |
6aa74308 EJ |
712 | state |= LACP_STATE_COL | LACP_STATE_DIST; |
713 | } | |
714 | ||
e1ce3f2d EJ |
715 | key = lacp->key_slave->key; |
716 | if (!key) { | |
717 | key = lacp->key_slave->port_id; | |
718 | } | |
719 | ||
77fdfa9b | 720 | actor->state = state; |
e1ce3f2d | 721 | actor->key = htons(key); |
77fdfa9b EJ |
722 | actor->port_priority = htons(slave->port_priority); |
723 | actor->port_id = htons(slave->port_id); | |
da2f7b8f EJ |
724 | actor->sys_priority = htons(lacp->sys_priority); |
725 | memcpy(&actor->sys_id, lacp->sys_id, ETH_ADDR_LEN); | |
6aa74308 EJ |
726 | } |
727 | ||
728 | /* Given 'slave', populates 'priority' with data representing its LACP link | |
729 | * priority. If two priority objects populated by this function are compared | |
730 | * using memcmp, the higher priority link will be less than the lower priority | |
731 | * link. */ | |
732 | static void | |
733 | slave_get_priority(struct slave *slave, struct lacp_info *priority) | |
bd3950dd | 734 | OVS_REQUIRES(mutex) |
6aa74308 EJ |
735 | { |
736 | uint16_t partner_priority, actor_priority; | |
737 | ||
738 | /* Choose the lacp_info of the higher priority system by comparing their | |
739 | * system priorities and mac addresses. */ | |
77fdfa9b | 740 | actor_priority = slave->lacp->sys_priority; |
6aa74308 EJ |
741 | partner_priority = ntohs(slave->partner.sys_priority); |
742 | if (actor_priority < partner_priority) { | |
77fdfa9b | 743 | slave_get_actor(slave, priority); |
6aa74308 EJ |
744 | } else if (partner_priority < actor_priority) { |
745 | *priority = slave->partner; | |
77fdfa9b | 746 | } else if (eth_addr_compare_3way(slave->lacp->sys_id, |
6aa74308 | 747 | slave->partner.sys_id) < 0) { |
77fdfa9b | 748 | slave_get_actor(slave, priority); |
6aa74308 EJ |
749 | } else { |
750 | *priority = slave->partner; | |
751 | } | |
752 | ||
753 | /* Key and state are not used in priority comparisons. */ | |
754 | priority->key = 0; | |
755 | priority->state = 0; | |
756 | } | |
757 | ||
758 | static bool | |
bd3950dd | 759 | slave_may_tx(const struct slave *slave) OVS_REQUIRES(mutex) |
6aa74308 EJ |
760 | { |
761 | return slave->lacp->active || slave->status != LACP_DEFAULTED; | |
762 | } | |
763 | ||
764 | static struct slave * | |
bd3950dd | 765 | slave_lookup(const struct lacp *lacp, const void *slave_) OVS_REQUIRES(mutex) |
6aa74308 EJ |
766 | { |
767 | struct slave *slave; | |
768 | ||
769 | HMAP_FOR_EACH_IN_BUCKET (slave, node, hash_pointer(slave_, 0), | |
770 | &lacp->slaves) { | |
771 | if (slave->aux == slave_) { | |
772 | return slave; | |
773 | } | |
774 | } | |
775 | ||
776 | return NULL; | |
777 | } | |
77fdfa9b EJ |
778 | |
779 | /* Two lacp_info structures are tx_equal if and only if they do not differ in | |
780 | * ways which would require a lacp_pdu transmission. */ | |
781 | static bool | |
782 | info_tx_equal(struct lacp_info *a, struct lacp_info *b) | |
783 | { | |
784 | ||
785 | /* LACP specification dictates that we transmit whenever the actor and | |
786 | * remote_actor differ in the following fields: Port, Port Priority, | |
787 | * System, System Priority, Aggregation Key, Activity State, Timeout State, | |
788 | * Sync State, and Aggregation State. The state flags are most likely to | |
789 | * change so are checked first. */ | |
790 | return !((a->state ^ b->state) & (LACP_STATE_ACT | |
791 | | LACP_STATE_TIME | |
792 | | LACP_STATE_SYNC | |
793 | | LACP_STATE_AGG)) | |
794 | && a->port_id == b->port_id | |
795 | && a->port_priority == b->port_priority | |
796 | && a->key == b->key | |
797 | && a->sys_priority == b->sys_priority | |
798 | && eth_addr_equals(a->sys_id, b->sys_id); | |
799 | } | |
6aa74308 EJ |
800 | \f |
801 | static struct lacp * | |
344e21d4 | 802 | lacp_find(const char *name) OVS_REQUIRES(mutex) |
6aa74308 EJ |
803 | { |
804 | struct lacp *lacp; | |
805 | ||
b468782a | 806 | LIST_FOR_EACH (lacp, node, all_lacps) { |
6aa74308 EJ |
807 | if (!strcmp(lacp->name, name)) { |
808 | return lacp; | |
809 | } | |
810 | } | |
811 | ||
812 | return NULL; | |
813 | } | |
814 | ||
815 | static void | |
816 | ds_put_lacp_state(struct ds *ds, uint8_t state) | |
817 | { | |
818 | if (state & LACP_STATE_ACT) { | |
1c1df7f1 | 819 | ds_put_cstr(ds, " activity"); |
6aa74308 EJ |
820 | } |
821 | ||
822 | if (state & LACP_STATE_TIME) { | |
1c1df7f1 | 823 | ds_put_cstr(ds, " timeout"); |
6aa74308 EJ |
824 | } |
825 | ||
826 | if (state & LACP_STATE_AGG) { | |
1c1df7f1 | 827 | ds_put_cstr(ds, " aggregation"); |
6aa74308 EJ |
828 | } |
829 | ||
830 | if (state & LACP_STATE_SYNC) { | |
1c1df7f1 | 831 | ds_put_cstr(ds, " synchronized"); |
6aa74308 EJ |
832 | } |
833 | ||
834 | if (state & LACP_STATE_COL) { | |
1c1df7f1 | 835 | ds_put_cstr(ds, " collecting"); |
6aa74308 EJ |
836 | } |
837 | ||
838 | if (state & LACP_STATE_DIST) { | |
1c1df7f1 | 839 | ds_put_cstr(ds, " distributing"); |
6aa74308 EJ |
840 | } |
841 | ||
842 | if (state & LACP_STATE_DEF) { | |
1c1df7f1 | 843 | ds_put_cstr(ds, " defaulted"); |
6aa74308 EJ |
844 | } |
845 | ||
846 | if (state & LACP_STATE_EXP) { | |
1c1df7f1 | 847 | ds_put_cstr(ds, " expired"); |
6aa74308 EJ |
848 | } |
849 | } | |
850 | ||
851 | static void | |
344e21d4 | 852 | lacp_print_details(struct ds *ds, struct lacp *lacp) OVS_REQUIRES(mutex) |
6aa74308 | 853 | { |
431d6a6a EJ |
854 | struct shash slave_shash = SHASH_INITIALIZER(&slave_shash); |
855 | const struct shash_node **sorted_slaves = NULL; | |
856 | ||
6aa74308 | 857 | struct slave *slave; |
431d6a6a | 858 | int i; |
6aa74308 | 859 | |
5dab8ece JP |
860 | ds_put_format(ds, "---- %s ----\n", lacp->name); |
861 | ds_put_format(ds, "\tstatus: %s", lacp->active ? "active" : "passive"); | |
20601813 | 862 | if (lacp->negotiated) { |
5dab8ece | 863 | ds_put_cstr(ds, " negotiated"); |
20601813 | 864 | } |
5dab8ece | 865 | ds_put_cstr(ds, "\n"); |
20601813 | 866 | |
5dab8ece JP |
867 | ds_put_format(ds, "\tsys_id: " ETH_ADDR_FMT "\n", ETH_ADDR_ARGS(lacp->sys_id)); |
868 | ds_put_format(ds, "\tsys_priority: %u\n", lacp->sys_priority); | |
869 | ds_put_cstr(ds, "\taggregation key: "); | |
6aa74308 | 870 | if (lacp->key_slave) { |
ab3a09c8 AS |
871 | ds_put_format(ds, "%u", lacp->key_slave->key |
872 | ? lacp->key_slave->key | |
873 | : lacp->key_slave->port_id); | |
6aa74308 | 874 | } else { |
5dab8ece | 875 | ds_put_cstr(ds, "none"); |
6aa74308 | 876 | } |
5dab8ece | 877 | ds_put_cstr(ds, "\n"); |
6aa74308 | 878 | |
5dab8ece | 879 | ds_put_cstr(ds, "\tlacp_time: "); |
bf83f7c8 | 880 | if (lacp->fast) { |
5dab8ece | 881 | ds_put_cstr(ds, "fast\n"); |
bf83f7c8 | 882 | } else { |
5dab8ece | 883 | ds_put_cstr(ds, "slow\n"); |
cdcf42c6 EJ |
884 | } |
885 | ||
6aa74308 | 886 | HMAP_FOR_EACH (slave, node, &lacp->slaves) { |
431d6a6a EJ |
887 | shash_add(&slave_shash, slave->name, slave); |
888 | } | |
889 | sorted_slaves = shash_sort(&slave_shash); | |
890 | ||
891 | for (i = 0; i < shash_count(&slave_shash); i++) { | |
6aa74308 | 892 | char *status; |
77fdfa9b | 893 | struct lacp_info actor; |
6aa74308 | 894 | |
431d6a6a | 895 | slave = sorted_slaves[i]->data; |
77fdfa9b | 896 | slave_get_actor(slave, &actor); |
6aa74308 EJ |
897 | switch (slave->status) { |
898 | case LACP_CURRENT: | |
899 | status = "current"; | |
900 | break; | |
901 | case LACP_EXPIRED: | |
902 | status = "expired"; | |
903 | break; | |
904 | case LACP_DEFAULTED: | |
905 | status = "defaulted"; | |
906 | break; | |
907 | default: | |
908 | NOT_REACHED(); | |
909 | } | |
910 | ||
5dab8ece | 911 | ds_put_format(ds, "\nslave: %s: %s %s\n", slave->name, status, |
0f1a47f1 | 912 | slave->attached ? "attached" : "detached"); |
5dab8ece JP |
913 | ds_put_format(ds, "\tport_id: %u\n", slave->port_id); |
914 | ds_put_format(ds, "\tport_priority: %u\n", slave->port_priority); | |
29985e75 EJ |
915 | ds_put_format(ds, "\tmay_enable: %s\n", (slave_may_enable__(slave) |
916 | ? "true" : "false")); | |
6aa74308 | 917 | |
5dab8ece | 918 | ds_put_format(ds, "\n\tactor sys_id: " ETH_ADDR_FMT "\n", |
77fdfa9b | 919 | ETH_ADDR_ARGS(actor.sys_id)); |
5dab8ece | 920 | ds_put_format(ds, "\tactor sys_priority: %u\n", |
77fdfa9b | 921 | ntohs(actor.sys_priority)); |
5dab8ece | 922 | ds_put_format(ds, "\tactor port_id: %u\n", |
77fdfa9b | 923 | ntohs(actor.port_id)); |
5dab8ece | 924 | ds_put_format(ds, "\tactor port_priority: %u\n", |
77fdfa9b | 925 | ntohs(actor.port_priority)); |
5dab8ece | 926 | ds_put_format(ds, "\tactor key: %u\n", |
77fdfa9b | 927 | ntohs(actor.key)); |
1c1df7f1 | 928 | ds_put_cstr(ds, "\tactor state:"); |
5dab8ece JP |
929 | ds_put_lacp_state(ds, actor.state); |
930 | ds_put_cstr(ds, "\n\n"); | |
6aa74308 | 931 | |
5dab8ece | 932 | ds_put_format(ds, "\tpartner sys_id: " ETH_ADDR_FMT "\n", |
6aa74308 | 933 | ETH_ADDR_ARGS(slave->partner.sys_id)); |
5dab8ece | 934 | ds_put_format(ds, "\tpartner sys_priority: %u\n", |
6aa74308 | 935 | ntohs(slave->partner.sys_priority)); |
5dab8ece | 936 | ds_put_format(ds, "\tpartner port_id: %u\n", |
6aa74308 | 937 | ntohs(slave->partner.port_id)); |
5dab8ece | 938 | ds_put_format(ds, "\tpartner port_priority: %u\n", |
6aa74308 | 939 | ntohs(slave->partner.port_priority)); |
5dab8ece | 940 | ds_put_format(ds, "\tpartner key: %u\n", |
6aa74308 | 941 | ntohs(slave->partner.key)); |
1c1df7f1 | 942 | ds_put_cstr(ds, "\tpartner state:"); |
5dab8ece JP |
943 | ds_put_lacp_state(ds, slave->partner.state); |
944 | ds_put_cstr(ds, "\n"); | |
945 | } | |
431d6a6a EJ |
946 | |
947 | shash_destroy(&slave_shash); | |
948 | free(sorted_slaves); | |
5dab8ece JP |
949 | } |
950 | ||
951 | static void | |
0e15264f | 952 | lacp_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], |
b468782a | 953 | void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) |
5dab8ece JP |
954 | { |
955 | struct ds ds = DS_EMPTY_INITIALIZER; | |
956 | struct lacp *lacp; | |
957 | ||
b468782a | 958 | ovs_mutex_lock(&mutex); |
0e15264f BP |
959 | if (argc > 1) { |
960 | lacp = lacp_find(argv[1]); | |
5dab8ece | 961 | if (!lacp) { |
bde9f75d | 962 | unixctl_command_reply_error(conn, "no such lacp object"); |
b468782a | 963 | goto out; |
5dab8ece JP |
964 | } |
965 | lacp_print_details(&ds, lacp); | |
966 | } else { | |
b468782a | 967 | LIST_FOR_EACH (lacp, node, all_lacps) { |
5dab8ece JP |
968 | lacp_print_details(&ds, lacp); |
969 | } | |
6aa74308 EJ |
970 | } |
971 | ||
bde9f75d | 972 | unixctl_command_reply(conn, ds_cstr(&ds)); |
6aa74308 | 973 | ds_destroy(&ds); |
b468782a EJ |
974 | |
975 | out: | |
976 | ovs_mutex_unlock(&mutex); | |
6aa74308 | 977 | } |