]>
git.proxmox.com Git - mirror_ovs.git/blob - lib/cfm.c
2 * Copyright (c) 2010, 2011 Nicira Networks.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at:
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
24 #include "dynamic-string.h"
30 #include "poll-loop.h"
36 VLOG_DEFINE_THIS_MODULE(cfm
);
38 #define CCM_OPCODE 1 /* CFM message opcode meaning CCM. */
42 struct list list_node
; /* Node in all_cfms list. */
44 uint32_t seq
; /* The sequence number of our last CCM. */
45 uint8_t ccm_interval
; /* The CCM transmission interval. */
46 int ccm_interval_ms
; /* 'ccm_interval' in milliseconds. */
48 struct timer tx_timer
; /* Send CCM when expired. */
49 struct timer fault_timer
; /* Check for faults when expired. */
52 static struct vlog_rate_limit rl
= VLOG_RATE_LIMIT_INIT(5, 20);
53 static struct list all_cfms
= LIST_INITIALIZER(&all_cfms
);
55 static void cfm_unixctl_show(struct unixctl_conn
*, const char *args
,
59 ccm_interval_to_ms(uint8_t interval
)
62 case 0: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
63 case 1: return 3; /* Not recommended due to timer resolution. */
64 case 2: return 10; /* Not recommended due to timer resolution. */
69 case 7: return 600000;
70 default: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */
77 cfm_fault_interval(struct cfm_internal
*cfmi
)
79 /* According to the 802.1ag specification we should assume every other MP
80 * with the same MAID has the same transmission interval that we have. If
81 * an MP has a different interval, cfm_process_heartbeat will register it
82 * as a fault (likely due to a configuration error). Thus we can check all
83 * MPs at once making this quite a bit simpler.
85 * According to the specification we should check when (ccm_interval_ms *
86 * 3.5)ms have passed. */
87 return (cfmi
->ccm_interval_ms
* 7) / 2;
91 ms_to_ccm_interval(int interval_ms
)
95 for (i
= 7; i
> 0; i
--) {
96 if (ccm_interval_to_ms(i
) <= interval_ms
) {
104 static struct cfm_internal
*
105 cfm_to_internal(const struct cfm
*cfm
)
107 return CONTAINER_OF(cfm
, struct cfm_internal
, cfm
);
111 hash_mpid(uint8_t mpid
)
113 return hash_int(mpid
, 0);
117 cfm_is_valid_mpid(uint32_t mpid
)
119 /* 802.1ag specification requires MPIDs to be within the range [1, 8191] */
120 return mpid
>= 1 && mpid
<= 8191;
123 static struct remote_mp
*
124 lookup_remote_mp(const struct hmap
*hmap
, uint16_t mpid
)
126 struct remote_mp
*rmp
;
128 HMAP_FOR_EACH_IN_BUCKET (rmp
, node
, hash_mpid(mpid
), hmap
) {
129 if (rmp
->mpid
== mpid
) {
140 unixctl_command_register("cfm/show", cfm_unixctl_show
, NULL
);
143 /* Allocates a 'cfm' object. This object should have its 'mpid', 'maid',
144 * 'eth_src', and 'interval' filled out. cfm_configure() should be called
145 * whenever changes are made to 'cfm', and before cfm_run() is called for the
151 struct cfm_internal
*cfmi
;
153 cfmi
= xzalloc(sizeof *cfmi
);
156 hmap_init(&cfm
->remote_mps
);
157 list_push_back(&all_cfms
, &cfmi
->list_node
);
162 cfm_destroy(struct cfm
*cfm
)
164 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
165 struct remote_mp
*rmp
, *rmp_next
;
171 HMAP_FOR_EACH_SAFE (rmp
, rmp_next
, node
, &cfm
->remote_mps
) {
172 hmap_remove(&cfm
->remote_mps
, &rmp
->node
);
176 hmap_destroy(&cfm
->remote_mps
);
177 list_remove(&cfmi
->list_node
);
181 /* Should be run periodically to update fault statistics messages. */
183 cfm_run(struct cfm
*cfm
)
185 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
187 if (timer_expired(&cfmi
->fault_timer
)) {
188 long long int interval
= cfm_fault_interval(cfmi
);
189 struct remote_mp
*rmp
;
192 HMAP_FOR_EACH (rmp
, node
, &cfm
->remote_mps
) {
193 rmp
->fault
= !rmp
->recv
;
198 VLOG_DBG("No CCM from RMP %"PRIu16
" in the last %lldms",
199 rmp
->mpid
, interval
);
204 VLOG_DBG("All RMPs received CCMs in the last %lldms", interval
);
207 timer_set_duration(&cfmi
->fault_timer
, interval
);
211 /* Should be run periodically to check if the CFM module has a CCM message it
214 cfm_should_send_ccm(struct cfm
*cfm
)
216 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
218 return timer_expired(&cfmi
->tx_timer
);
221 /* Composes a CCM message into 'ccm'. Messages generated with this function
222 * should be sent whenever cfm_should_send_ccm() indicates. */
224 cfm_compose_ccm(struct cfm
*cfm
, struct ccm
*ccm
)
226 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
228 timer_set_duration(&cfmi
->tx_timer
, cfmi
->ccm_interval_ms
);
230 ccm
->mdlevel_version
= 0;
231 ccm
->opcode
= CCM_OPCODE
;
232 ccm
->tlv_offset
= 70;
233 ccm
->seq
= htonl(++cfmi
->seq
);
234 ccm
->mpid
= htons(cfmi
->cfm
.mpid
);
235 ccm
->flags
= cfmi
->ccm_interval
;
236 memcpy(ccm
->maid
, cfmi
->cfm
.maid
, sizeof ccm
->maid
);
240 cfm_wait(struct cfm
*cfm
)
242 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
244 timer_wait(&cfmi
->tx_timer
);
245 timer_wait(&cfmi
->fault_timer
);
248 /* Should be called whenever a client of the cfm library changes the internals
249 * of 'cfm'. Returns true if 'cfm' is valid. */
251 cfm_configure(struct cfm
*cfm
)
253 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
256 if (!cfm_is_valid_mpid(cfm
->mpid
) || !cfm
->interval
) {
260 interval
= ms_to_ccm_interval(cfm
->interval
);
262 if (interval
!= cfmi
->ccm_interval
) {
263 cfmi
->ccm_interval
= interval
;
264 cfmi
->ccm_interval_ms
= ccm_interval_to_ms(interval
);
266 timer_set_expired(&cfmi
->tx_timer
);
267 timer_set_duration(&cfmi
->fault_timer
, cfm_fault_interval(cfmi
));
273 /* Given an array of MPIDs, updates the 'remote_mps' map of 'cfm' to reflect
274 * it. Invalid MPIDs are skipped. */
276 cfm_update_remote_mps(struct cfm
*cfm
, const uint16_t *mpids
, size_t n_mpids
)
279 struct hmap new_rmps
;
280 struct remote_mp
*rmp
, *rmp_next
;
282 hmap_init(&new_rmps
);
284 for (i
= 0; i
< n_mpids
; i
++) {
285 uint16_t mpid
= mpids
[i
];
287 if (!cfm_is_valid_mpid(mpid
)
288 || lookup_remote_mp(&new_rmps
, mpid
)) {
292 if ((rmp
= lookup_remote_mp(&cfm
->remote_mps
, mpid
))) {
293 hmap_remove(&cfm
->remote_mps
, &rmp
->node
);
295 rmp
= xzalloc(sizeof *rmp
);
299 hmap_insert(&new_rmps
, &rmp
->node
, hash_mpid(mpid
));
302 hmap_swap(&new_rmps
, &cfm
->remote_mps
);
304 HMAP_FOR_EACH_SAFE (rmp
, rmp_next
, node
, &new_rmps
) {
305 hmap_remove(&new_rmps
, &rmp
->node
);
309 hmap_destroy(&new_rmps
);
312 /* Finds a 'remote_mp' with 'mpid' in 'cfm'. If no such 'remote_mp' exists
314 const struct remote_mp
*
315 cfm_get_remote_mp(const struct cfm
*cfm
, uint16_t mpid
)
317 return lookup_remote_mp(&cfm
->remote_mps
, mpid
);
320 /* Generates 'maid' from 'md_name' and 'ma_name'. A NULL parameter indicates
321 * the default should be used. Returns false if unsuccessful. */
323 cfm_generate_maid(const char *md_name
, const char *ma_name
,
324 uint8_t maid
[CCM_MAID_LEN
])
327 size_t md_len
, ma_len
;
337 memset(maid
, 0, CCM_MAID_LEN
);
339 md_len
= strlen(md_name
);
340 ma_len
= strlen(ma_name
);
342 if (!md_len
|| !ma_len
|| md_len
+ ma_len
+ 4 > CCM_MAID_LEN
) {
346 maid
[0] = 4; /* MD name string format. */
347 maid
[1] = md_len
; /* MD name size. */
348 memcpy(&maid
[2], md_name
, md_len
); /* MD name. */
350 ma_p
= maid
+ 2 + md_len
;
351 ma_p
[0] = 2; /* MA name string format. */
352 ma_p
[1] = ma_len
; /* MA name size. */
353 memcpy(&ma_p
[2], ma_name
, ma_len
); /* MA name. */
357 /* Returns true if the CFM library should process packets from 'flow'. */
359 cfm_should_process_flow(const struct flow
*flow
)
361 return (ntohs(flow
->dl_type
) == ETH_TYPE_CFM
362 && eth_addr_equals(flow
->dl_dst
, eth_addr_ccm
));
365 /* Updates internal statistics relevant to packet 'p'. Should be called on
366 * every packet whose flow returned true when passed to
367 * cfm_should_process_flow. */
369 cfm_process_heartbeat(struct cfm
*cfm
, const struct ofpbuf
*p
)
373 uint8_t ccm_interval
;
374 struct remote_mp
*rmp
;
375 struct eth_header
*eth
;
376 struct cfm_internal
*cfmi
= cfm_to_internal(cfm
);
379 ccm
= ofpbuf_at(p
, (uint8_t *)p
->l3
- (uint8_t *)p
->data
, CCM_LEN
);
382 VLOG_INFO_RL(&rl
, "Received an un-parseable 802.1ag CCM heartbeat.");
386 if (ccm
->opcode
!= CCM_OPCODE
) {
387 VLOG_INFO_RL(&rl
, "Received an unsupported 802.1ag message. "
388 "(opcode %u)", ccm
->opcode
);
392 /* According to the 802.1ag specification, reception of a CCM with an
393 * incorrect ccm_interval, unexpected MAID, or unexpected MPID should
394 * trigger a fault. We ignore this requirement for several reasons.
396 * Faults can cause a controller or Open vSwitch to make potentially
397 * expensive changes to the network topology. It seems prudent to trigger
398 * them judiciously, especially when CFM is used to check slave status of
399 * bonds. Furthermore, faults can be maliciously triggered by crafting
401 if (memcmp(ccm
->maid
, cfm
->maid
, sizeof ccm
->maid
)) {
402 VLOG_WARN_RL(&rl
, "Received unexpected remote MAID from MAC "
403 ETH_ADDR_FMT
, ETH_ADDR_ARGS(eth
->eth_src
));
405 ccm_mpid
= ntohs(ccm
->mpid
);
406 ccm_interval
= ccm
->flags
& 0x7;
408 rmp
= lookup_remote_mp(&cfm
->remote_mps
, ccm_mpid
);
413 if (ccm_interval
!= cfmi
->ccm_interval
) {
414 VLOG_WARN_RL(&rl
, "received a CCM with an invalid interval"
415 " (%"PRIu8
") from RMP %"PRIu16
, ccm_interval
,
419 VLOG_WARN_RL(&rl
, "Received unexpected remote MPID %d from MAC "
420 ETH_ADDR_FMT
, ccm_mpid
, ETH_ADDR_ARGS(eth
->eth_src
));
423 VLOG_DBG("Received CCM (mpid %"PRIu16
") (interval %"PRIu8
")", ccm_mpid
,
428 static struct cfm_internal
*
429 cfm_find(const char *name
)
431 struct cfm_internal
*cfmi
;
433 LIST_FOR_EACH (cfmi
, list_node
, &all_cfms
) {
434 if (cfmi
->cfm
.name
&& !strcmp(cfmi
->cfm
.name
, name
)) {
442 cfm_unixctl_show(struct unixctl_conn
*conn
,
443 const char *args
, void *aux OVS_UNUSED
)
445 struct ds ds
= DS_EMPTY_INITIALIZER
;
446 const struct cfm_internal
*cfmi
;
447 struct remote_mp
*rmp
;
449 cfmi
= cfm_find(args
);
451 unixctl_command_reply(conn
, 501, "no such CFM object");
455 ds_put_format(&ds
, "MPID %"PRIu16
": %s\n", cfmi
->cfm
.mpid
,
456 cfmi
->cfm
.fault
? "fault" : "");
458 ds_put_format(&ds
, "\tinterval: %dms\n", cfmi
->ccm_interval_ms
);
459 ds_put_format(&ds
, "\tnext CCM tx: %lldms\n",
460 timer_msecs_until_expired(&cfmi
->tx_timer
));
461 ds_put_format(&ds
, "\tnext fault check: %lldms\n",
462 timer_msecs_until_expired(&cfmi
->fault_timer
));
464 ds_put_cstr(&ds
, "\n");
465 HMAP_FOR_EACH (rmp
, node
, &cfmi
->cfm
.remote_mps
) {
466 ds_put_format(&ds
, "Remote MPID %"PRIu16
": %s\n", rmp
->mpid
,
467 rmp
->fault
? "fault" : "");
468 ds_put_format(&ds
, "\trecv since check: %s",
469 rmp
->recv
? "true" : "false");
472 unixctl_command_reply(conn
, 200, ds_cstr(&ds
));