]>
Commit | Line | Data |
---|---|---|
b31bcf60 | 1 | /* |
e0edde6f | 2 | * Copyright (c) 2010, 2011, 2012 Nicira, Inc. |
b31bcf60 EJ |
3 | * |
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: | |
7 | * | |
8 | * http://www.apache.org/licenses/LICENSE-2.0 | |
9 | * | |
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. | |
15 | */ | |
16 | ||
17 | #include <config.h> | |
18 | #include "cfm.h" | |
19 | ||
84c5d450 | 20 | #include <assert.h> |
b31bcf60 EJ |
21 | #include <stdint.h> |
22 | #include <stdlib.h> | |
23 | #include <string.h> | |
24 | ||
348f01e3 | 25 | #include "byte-order.h" |
20c8e971 | 26 | #include "dynamic-string.h" |
b31bcf60 EJ |
27 | #include "flow.h" |
28 | #include "hash.h" | |
29 | #include "hmap.h" | |
30 | #include "ofpbuf.h" | |
31 | #include "packets.h" | |
32 | #include "poll-loop.h" | |
189cb9e4 | 33 | #include "random.h" |
6fabb78d | 34 | #include "timer.h" |
b31bcf60 | 35 | #include "timeval.h" |
9ac3fce4 | 36 | #include "unixctl.h" |
b31bcf60 EJ |
37 | #include "vlog.h" |
38 | ||
39 | VLOG_DEFINE_THIS_MODULE(cfm); | |
40 | ||
144216a3 EJ |
41 | #define CFM_MAX_RMPS 256 |
42 | ||
c0a2e71d EJ |
43 | /* Ethernet destination address of CCM packets. */ |
44 | static const uint8_t eth_addr_ccm[6] = { 0x01, 0x80, 0xC2, 0x00, 0x00, 0x30 }; | |
ef9819b5 EJ |
45 | static const uint8_t eth_addr_ccm_x[6] = { |
46 | 0x01, 0x23, 0x20, 0x00, 0x00, 0x30 | |
47 | }; | |
c0a2e71d EJ |
48 | |
49 | #define ETH_TYPE_CFM 0x8902 | |
50 | ||
51 | /* A 'ccm' represents a Continuity Check Message from the 802.1ag | |
52 | * specification. Continuity Check Messages are broadcast periodically so that | |
ce27cbee BP |
53 | * hosts can determine whom they have connectivity to. |
54 | * | |
55 | * The minimum length of a CCM as specified by IEEE 802.1ag is 75 bytes. | |
56 | * Previous versions of Open vSwitch generated 74-byte CCM messages, so we | |
57 | * accept such messages too. */ | |
58 | #define CCM_LEN 75 | |
59 | #define CCM_ACCEPT_LEN 74 | |
c0a2e71d EJ |
60 | #define CCM_MAID_LEN 48 |
61 | #define CCM_OPCODE 1 /* CFM message opcode meaning CCM. */ | |
aa62994c | 62 | #define CCM_RDI_MASK 0x80 |
3967a833 | 63 | #define CFM_HEALTH_INTERVAL 6 |
c0a2e71d EJ |
64 | struct ccm { |
65 | uint8_t mdlevel_version; /* MD Level and Version */ | |
66 | uint8_t opcode; | |
67 | uint8_t flags; | |
68 | uint8_t tlv_offset; | |
69 | ovs_be32 seq; | |
70 | ovs_be16 mpid; | |
71 | uint8_t maid[CCM_MAID_LEN]; | |
de724029 EJ |
72 | |
73 | /* Defined by ITU-T Y.1731 should be zero */ | |
74 | ovs_be16 interval_ms_x; /* Transmission interval in ms. */ | |
348f01e3 | 75 | ovs_be64 mpid64; /* MPID in extended mode. */ |
86dc6501 EJ |
76 | uint8_t opdown; /* Operationally down. */ |
77 | uint8_t zero[5]; | |
ce27cbee BP |
78 | |
79 | /* TLV space. */ | |
80 | uint8_t end_tlv; | |
c0a2e71d EJ |
81 | } __attribute__((packed)); |
82 | BUILD_ASSERT_DECL(CCM_LEN == sizeof(struct ccm)); | |
b31bcf60 | 83 | |
a5610457 | 84 | struct cfm { |
6f629657 EJ |
85 | char *name; /* Name of this CFM object. */ |
86 | struct hmap_node hmap_node; /* Node in all_cfms list. */ | |
b31bcf60 | 87 | |
348f01e3 | 88 | uint64_t mpid; |
ef9819b5 | 89 | bool extended; /* Extended mode. */ |
2b540ecb MM |
90 | enum cfm_fault_reason fault; /* Connectivity fault status. */ |
91 | enum cfm_fault_reason recv_fault; /* Bit mask of faults occuring on | |
92 | receive. */ | |
86dc6501 EJ |
93 | bool opup; /* Operational State. */ |
94 | bool remote_opup; /* Remote Operational State. */ | |
a5610457 | 95 | |
d7243b93 EJ |
96 | int fault_override; /* Manual override of 'fault' status. |
97 | Ignored if negative. */ | |
98 | ||
9ac3fce4 | 99 | uint32_t seq; /* The sequence number of our last CCM. */ |
b31bcf60 EJ |
100 | uint8_t ccm_interval; /* The CCM transmission interval. */ |
101 | int ccm_interval_ms; /* 'ccm_interval' in milliseconds. */ | |
189cb9e4 EJ |
102 | uint16_t ccm_vlan; /* Vlan tag of CCM PDUs. CFM_RANDOM_VLAN if |
103 | random. */ | |
a7aa2d3c | 104 | uint8_t ccm_pcp; /* Priority of CCM PDUs. */ |
84c5d450 | 105 | uint8_t maid[CCM_MAID_LEN]; /* The MAID of this CFM. */ |
b31bcf60 | 106 | |
6fabb78d EJ |
107 | struct timer tx_timer; /* Send CCM when expired. */ |
108 | struct timer fault_timer; /* Check for faults when expired. */ | |
93b8df38 | 109 | |
144216a3 | 110 | struct hmap remote_mps; /* Remote MPs. */ |
1de11730 EJ |
111 | |
112 | /* Result of cfm_get_remote_mpids(). Updated only during fault check to | |
113 | * avoid flapping. */ | |
114 | uint64_t *rmps_array; /* Cache of remote_mps. */ | |
115 | size_t rmps_array_len; /* Number of rmps in 'rmps_array'. */ | |
3967a833 MM |
116 | |
117 | int health; /* Percentage of the number of CCM frames | |
118 | received. */ | |
119 | int health_interval; /* Number of fault_intervals since health was | |
120 | recomputed. */ | |
121 | ||
93b8df38 EJ |
122 | }; |
123 | ||
124 | /* Remote MPs represent foreign network entities that are configured to have | |
125 | * the same MAID as this CFM instance. */ | |
126 | struct remote_mp { | |
348f01e3 | 127 | uint64_t mpid; /* The Maintenance Point ID of this 'remote_mp'. */ |
93b8df38 EJ |
128 | struct hmap_node node; /* Node in 'remote_mps' map. */ |
129 | ||
130 | bool recv; /* CCM was received since last fault check. */ | |
86dc6501 | 131 | bool opup; /* Operational State. */ |
8210113e | 132 | uint32_t seq; /* Most recently received sequence number. */ |
3967a833 MM |
133 | uint8_t num_health_ccm; /* Number of received ccm frames every |
134 | CFM_HEALTH_INTERVAL * 'fault_interval'. */ | |
135 | ||
b31bcf60 EJ |
136 | }; |
137 | ||
8210113e | 138 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 30); |
6f629657 | 139 | static struct hmap all_cfms = HMAP_INITIALIZER(&all_cfms); |
9ac3fce4 | 140 | |
0e15264f | 141 | static unixctl_cb_func cfm_unixctl_show; |
d7243b93 | 142 | static unixctl_cb_func cfm_unixctl_set_fault; |
dd986e09 | 143 | |
ef9819b5 EJ |
144 | static const uint8_t * |
145 | cfm_ccm_addr(const struct cfm *cfm) | |
146 | { | |
147 | return cfm->extended ? eth_addr_ccm_x : eth_addr_ccm; | |
148 | } | |
149 | ||
b9380396 EJ |
150 | /* Returns the string representation of the given cfm_fault_reason 'reason'. */ |
151 | const char * | |
152 | cfm_fault_reason_to_str(int reason) { | |
153 | switch (reason) { | |
154 | #define CFM_FAULT_REASON(NAME, STR) case CFM_FAULT_##NAME: return #STR; | |
155 | CFM_FAULT_REASONS | |
156 | #undef CFM_FAULT_REASON | |
157 | default: return "<unknown>"; | |
158 | } | |
159 | } | |
160 | ||
161 | static void | |
906bb3d0 | 162 | ds_put_cfm_fault(struct ds *ds, int old_fault, int new_fault) |
b9380396 | 163 | { |
b9380396 EJ |
164 | int i; |
165 | ||
166 | for (i = 0; i < CFM_FAULT_N_REASONS; i++) { | |
167 | int reason = 1 << i; | |
168 | ||
906bb3d0 BP |
169 | if ((old_fault | new_fault) & reason) { |
170 | ds_put_format(ds, " %s%s", | |
171 | (!(old_fault & reason) ? "+" | |
172 | : !(new_fault & reason) ? "-" | |
173 | : ""), | |
174 | cfm_fault_reason_to_str(reason)); | |
b9380396 EJ |
175 | } |
176 | } | |
b9380396 EJ |
177 | } |
178 | ||
84c5d450 | 179 | static void |
a5610457 | 180 | cfm_generate_maid(struct cfm *cfm) |
84c5d450 | 181 | { |
db16e36e EJ |
182 | const char *ovs_md_name = "ovs"; |
183 | const char *ovs_ma_name = "ovs"; | |
84c5d450 EJ |
184 | uint8_t *ma_p; |
185 | size_t md_len, ma_len; | |
186 | ||
a5610457 | 187 | memset(cfm->maid, 0, CCM_MAID_LEN); |
84c5d450 EJ |
188 | |
189 | md_len = strlen(ovs_md_name); | |
190 | ma_len = strlen(ovs_ma_name); | |
191 | ||
192 | assert(md_len && ma_len && md_len + ma_len + 4 <= CCM_MAID_LEN); | |
193 | ||
a5610457 EJ |
194 | cfm->maid[0] = 4; /* MD name string format. */ |
195 | cfm->maid[1] = md_len; /* MD name size. */ | |
196 | memcpy(&cfm->maid[2], ovs_md_name, md_len); /* MD name. */ | |
84c5d450 | 197 | |
a5610457 | 198 | ma_p = cfm->maid + 2 + md_len; |
84c5d450 EJ |
199 | ma_p[0] = 2; /* MA name string format. */ |
200 | ma_p[1] = ma_len; /* MA name size. */ | |
201 | memcpy(&ma_p[2], ovs_ma_name, ma_len); /* MA name. */ | |
202 | } | |
203 | ||
b31bcf60 EJ |
204 | static int |
205 | ccm_interval_to_ms(uint8_t interval) | |
206 | { | |
207 | switch (interval) { | |
208 | case 0: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */ | |
209 | case 1: return 3; /* Not recommended due to timer resolution. */ | |
210 | case 2: return 10; /* Not recommended due to timer resolution. */ | |
211 | case 3: return 100; | |
212 | case 4: return 1000; | |
213 | case 5: return 10000; | |
214 | case 6: return 60000; | |
215 | case 7: return 600000; | |
216 | default: NOT_REACHED(); /* Explicitly not supported by 802.1ag. */ | |
217 | } | |
218 | ||
219 | NOT_REACHED(); | |
220 | } | |
221 | ||
aac19178 | 222 | static long long int |
a5610457 | 223 | cfm_fault_interval(struct cfm *cfm) |
aac19178 EJ |
224 | { |
225 | /* According to the 802.1ag specification we should assume every other MP | |
226 | * with the same MAID has the same transmission interval that we have. If | |
227 | * an MP has a different interval, cfm_process_heartbeat will register it | |
228 | * as a fault (likely due to a configuration error). Thus we can check all | |
229 | * MPs at once making this quite a bit simpler. | |
230 | * | |
231 | * According to the specification we should check when (ccm_interval_ms * | |
232 | * 3.5)ms have passed. */ | |
a5610457 | 233 | return (cfm->ccm_interval_ms * 7) / 2; |
aac19178 EJ |
234 | } |
235 | ||
b31bcf60 EJ |
236 | static uint8_t |
237 | ms_to_ccm_interval(int interval_ms) | |
238 | { | |
239 | uint8_t i; | |
240 | ||
241 | for (i = 7; i > 0; i--) { | |
242 | if (ccm_interval_to_ms(i) <= interval_ms) { | |
243 | return i; | |
244 | } | |
245 | } | |
246 | ||
247 | return 1; | |
248 | } | |
249 | ||
b31bcf60 | 250 | static uint32_t |
348f01e3 | 251 | hash_mpid(uint64_t mpid) |
b31bcf60 | 252 | { |
348f01e3 | 253 | return hash_bytes(&mpid, sizeof mpid, 0); |
b31bcf60 EJ |
254 | } |
255 | ||
256 | static bool | |
348f01e3 | 257 | cfm_is_valid_mpid(bool extended, uint64_t mpid) |
b31bcf60 | 258 | { |
348f01e3 EJ |
259 | /* 802.1ag specification requires MPIDs to be within the range [1, 8191]. |
260 | * In extended mode we relax this requirement. */ | |
261 | return mpid >= 1 && (extended || mpid <= 8191); | |
b31bcf60 EJ |
262 | } |
263 | ||
264 | static struct remote_mp * | |
348f01e3 | 265 | lookup_remote_mp(const struct cfm *cfm, uint64_t mpid) |
b31bcf60 EJ |
266 | { |
267 | struct remote_mp *rmp; | |
268 | ||
144216a3 | 269 | HMAP_FOR_EACH_IN_BUCKET (rmp, node, hash_mpid(mpid), &cfm->remote_mps) { |
b31bcf60 EJ |
270 | if (rmp->mpid == mpid) { |
271 | return rmp; | |
272 | } | |
273 | } | |
274 | ||
275 | return NULL; | |
276 | } | |
277 | ||
9ac3fce4 EJ |
278 | void |
279 | cfm_init(void) | |
280 | { | |
0e15264f | 281 | unixctl_command_register("cfm/show", "[interface]", 0, 1, cfm_unixctl_show, |
ae75dae3 | 282 | NULL); |
d7243b93 EJ |
283 | unixctl_command_register("cfm/set-fault", "[interface] normal|false|true", |
284 | 1, 2, cfm_unixctl_set_fault, NULL); | |
9ac3fce4 EJ |
285 | } |
286 | ||
6f629657 EJ |
287 | /* Allocates a 'cfm' object called 'name'. 'cfm' should be initialized by |
288 | * cfm_configure() before use. */ | |
b31bcf60 | 289 | struct cfm * |
6f629657 | 290 | cfm_create(const char *name) |
b31bcf60 EJ |
291 | { |
292 | struct cfm *cfm; | |
b31bcf60 | 293 | |
a5610457 | 294 | cfm = xzalloc(sizeof *cfm); |
6f629657 | 295 | cfm->name = xstrdup(name); |
a5610457 EJ |
296 | hmap_init(&cfm->remote_mps); |
297 | cfm_generate_maid(cfm); | |
6f629657 | 298 | hmap_insert(&all_cfms, &cfm->hmap_node, hash_string(cfm->name, 0)); |
86dc6501 | 299 | cfm->remote_opup = true; |
d7243b93 | 300 | cfm->fault_override = -1; |
3967a833 | 301 | cfm->health = -1; |
b31bcf60 EJ |
302 | return cfm; |
303 | } | |
304 | ||
305 | void | |
306 | cfm_destroy(struct cfm *cfm) | |
307 | { | |
308 | struct remote_mp *rmp, *rmp_next; | |
b31bcf60 EJ |
309 | |
310 | if (!cfm) { | |
311 | return; | |
312 | } | |
313 | ||
a5610457 EJ |
314 | HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) { |
315 | hmap_remove(&cfm->remote_mps, &rmp->node); | |
b31bcf60 EJ |
316 | free(rmp); |
317 | } | |
318 | ||
a5610457 | 319 | hmap_destroy(&cfm->remote_mps); |
6f629657 | 320 | hmap_remove(&all_cfms, &cfm->hmap_node); |
1de11730 | 321 | free(cfm->rmps_array); |
6f629657 | 322 | free(cfm->name); |
a5610457 | 323 | free(cfm); |
b31bcf60 EJ |
324 | } |
325 | ||
a58727fb EJ |
326 | /* Should be run periodically to update fault statistics messages. */ |
327 | void | |
b31bcf60 EJ |
328 | cfm_run(struct cfm *cfm) |
329 | { | |
a5610457 EJ |
330 | if (timer_expired(&cfm->fault_timer)) { |
331 | long long int interval = cfm_fault_interval(cfm); | |
144216a3 | 332 | struct remote_mp *rmp, *rmp_next; |
8210113e | 333 | bool old_cfm_fault = cfm->fault; |
b31bcf60 | 334 | |
b9380396 EJ |
335 | cfm->fault = cfm->recv_fault; |
336 | cfm->recv_fault = 0; | |
e14749fa | 337 | |
1de11730 EJ |
338 | cfm->rmps_array_len = 0; |
339 | free(cfm->rmps_array); | |
340 | cfm->rmps_array = xmalloc(hmap_count(&cfm->remote_mps) * | |
341 | sizeof *cfm->rmps_array); | |
342 | ||
86dc6501 | 343 | cfm->remote_opup = true; |
3967a833 MM |
344 | if (cfm->health_interval == CFM_HEALTH_INTERVAL) { |
345 | /* Calculate the cfm health of the interface. If the number of | |
346 | * remote_mpids of a cfm interface is > 1, the cfm health is | |
347 | * undefined. If the number of remote_mpids is 1, the cfm health is | |
348 | * the percentage of the ccm frames received in the | |
349 | * (CFM_HEALTH_INTERVAL * 3.5)ms, else it is 0. */ | |
350 | if (hmap_count(&cfm->remote_mps) > 1) { | |
351 | cfm->health = -1; | |
352 | } else if (hmap_is_empty(&cfm->remote_mps)) { | |
353 | cfm->health = 0; | |
354 | } else { | |
355 | int exp_ccm_recvd; | |
356 | ||
357 | rmp = CONTAINER_OF(hmap_first(&cfm->remote_mps), | |
358 | struct remote_mp, node); | |
359 | exp_ccm_recvd = (CFM_HEALTH_INTERVAL * 7) / 2; | |
360 | /* Calculate the percentage of healthy ccm frames received. | |
361 | * Since the 'fault_interval' is (3.5 * cfm_interval), and | |
362 | * 1 CCM packet must be received every cfm_interval, | |
363 | * the 'remote_mpid' health reports the percentage of | |
364 | * healthy CCM frames received every | |
365 | * 'CFM_HEALTH_INTERVAL'th 'fault_interval'. */ | |
366 | cfm->health = (rmp->num_health_ccm * 100) / exp_ccm_recvd; | |
367 | cfm->health = MIN(cfm->health, 100); | |
368 | rmp->num_health_ccm = 0; | |
369 | assert(cfm->health >= 0 && cfm->health <= 100); | |
370 | } | |
371 | cfm->health_interval = 0; | |
372 | } | |
373 | cfm->health_interval++; | |
374 | ||
144216a3 | 375 | HMAP_FOR_EACH_SAFE (rmp, rmp_next, node, &cfm->remote_mps) { |
6fabb78d | 376 | |
144216a3 | 377 | if (!rmp->recv) { |
348f01e3 | 378 | VLOG_DBG("%s: no CCM from RMP %"PRIu64" in the last %lldms", |
32977975 | 379 | cfm->name, rmp->mpid, interval); |
144216a3 EJ |
380 | hmap_remove(&cfm->remote_mps, &rmp->node); |
381 | free(rmp); | |
382 | } else { | |
383 | rmp->recv = false; | |
384 | ||
86dc6501 EJ |
385 | if (!rmp->opup) { |
386 | cfm->remote_opup = rmp->opup; | |
387 | } | |
388 | ||
1de11730 | 389 | cfm->rmps_array[cfm->rmps_array_len++] = rmp->mpid; |
6fabb78d | 390 | } |
b31bcf60 EJ |
391 | } |
392 | ||
144216a3 | 393 | if (hmap_is_empty(&cfm->remote_mps)) { |
b9380396 | 394 | cfm->fault |= CFM_FAULT_RECV; |
dd986e09 EJ |
395 | } |
396 | ||
56eb405a | 397 | if (old_cfm_fault != cfm->fault && !VLOG_DROP_INFO(&rl)) { |
b9380396 EJ |
398 | struct ds ds = DS_EMPTY_INITIALIZER; |
399 | ||
906bb3d0 | 400 | ds_put_cfm_fault(&ds, old_cfm_fault, cfm->fault); |
56eb405a BP |
401 | VLOG_INFO("%s: CFM fault status changed:%s", cfm->name, |
402 | ds_cstr_ro(&ds)); | |
b9380396 | 403 | ds_destroy(&ds); |
8210113e EJ |
404 | } |
405 | ||
a5610457 | 406 | timer_set_duration(&cfm->fault_timer, interval); |
b31bcf60 | 407 | } |
a58727fb | 408 | } |
b31bcf60 | 409 | |
a58727fb EJ |
410 | /* Should be run periodically to check if the CFM module has a CCM message it |
411 | * wishes to send. */ | |
412 | bool | |
413 | cfm_should_send_ccm(struct cfm *cfm) | |
414 | { | |
a5610457 | 415 | return timer_expired(&cfm->tx_timer); |
a58727fb EJ |
416 | } |
417 | ||
c0a2e71d | 418 | /* Composes a CCM message into 'packet'. Messages generated with this function |
a58727fb EJ |
419 | * should be sent whenever cfm_should_send_ccm() indicates. */ |
420 | void | |
c0a2e71d EJ |
421 | cfm_compose_ccm(struct cfm *cfm, struct ofpbuf *packet, |
422 | uint8_t eth_src[ETH_ADDR_LEN]) | |
a58727fb | 423 | { |
189cb9e4 | 424 | uint16_t ccm_vlan; |
c0a2e71d EJ |
425 | struct ccm *ccm; |
426 | ||
a5610457 | 427 | timer_set_duration(&cfm->tx_timer, cfm->ccm_interval_ms); |
75a4ead1 EJ |
428 | eth_compose(packet, cfm_ccm_addr(cfm), eth_src, ETH_TYPE_CFM, sizeof *ccm); |
429 | ||
189cb9e4 EJ |
430 | ccm_vlan = (cfm->ccm_vlan != CFM_RANDOM_VLAN |
431 | ? cfm->ccm_vlan | |
432 | : random_uint16()); | |
433 | ccm_vlan = ccm_vlan & VLAN_VID_MASK; | |
434 | ||
435 | if (ccm_vlan || cfm->ccm_pcp) { | |
436 | uint16_t tci = ccm_vlan | (cfm->ccm_pcp << VLAN_PCP_SHIFT); | |
a7aa2d3c | 437 | eth_push_vlan(packet, htons(tci)); |
75a4ead1 EJ |
438 | } |
439 | ||
440 | ccm = packet->l3; | |
a58727fb EJ |
441 | ccm->mdlevel_version = 0; |
442 | ccm->opcode = CCM_OPCODE; | |
443 | ccm->tlv_offset = 70; | |
a5610457 | 444 | ccm->seq = htonl(++cfm->seq); |
a5610457 EJ |
445 | ccm->flags = cfm->ccm_interval; |
446 | memcpy(ccm->maid, cfm->maid, sizeof ccm->maid); | |
56717eb1 | 447 | memset(ccm->zero, 0, sizeof ccm->zero); |
ce27cbee | 448 | ccm->end_tlv = 0; |
aa62994c | 449 | |
348f01e3 EJ |
450 | if (cfm->extended) { |
451 | ccm->mpid = htons(hash_mpid(cfm->mpid)); | |
452 | ccm->mpid64 = htonll(cfm->mpid); | |
86dc6501 | 453 | ccm->opdown = !cfm->opup; |
348f01e3 EJ |
454 | } else { |
455 | ccm->mpid = htons(cfm->mpid); | |
456 | ccm->mpid64 = htonll(0); | |
86dc6501 | 457 | ccm->opdown = 0; |
348f01e3 EJ |
458 | } |
459 | ||
de724029 EJ |
460 | if (cfm->ccm_interval == 0) { |
461 | assert(cfm->extended); | |
462 | ccm->interval_ms_x = htons(cfm->ccm_interval_ms); | |
463 | } | |
464 | ||
144216a3 | 465 | if (hmap_is_empty(&cfm->remote_mps)) { |
aa62994c EJ |
466 | ccm->flags |= CCM_RDI_MASK; |
467 | } | |
b31bcf60 EJ |
468 | } |
469 | ||
470 | void | |
471 | cfm_wait(struct cfm *cfm) | |
472 | { | |
a5610457 EJ |
473 | timer_wait(&cfm->tx_timer); |
474 | timer_wait(&cfm->fault_timer); | |
b31bcf60 EJ |
475 | } |
476 | ||
a5610457 | 477 | /* Configures 'cfm' with settings from 's'. */ |
b31bcf60 | 478 | bool |
a5610457 | 479 | cfm_configure(struct cfm *cfm, const struct cfm_settings *s) |
b31bcf60 | 480 | { |
9aa952b2 | 481 | uint8_t interval; |
de724029 | 482 | int interval_ms; |
b31bcf60 | 483 | |
348f01e3 | 484 | if (!cfm_is_valid_mpid(s->extended, s->mpid) || s->interval <= 0) { |
b31bcf60 EJ |
485 | return false; |
486 | } | |
487 | ||
a5610457 | 488 | cfm->mpid = s->mpid; |
ef9819b5 | 489 | cfm->extended = s->extended; |
86dc6501 | 490 | cfm->opup = s->opup; |
a5610457 | 491 | interval = ms_to_ccm_interval(s->interval); |
de724029 EJ |
492 | interval_ms = ccm_interval_to_ms(interval); |
493 | ||
189cb9e4 | 494 | cfm->ccm_vlan = s->ccm_vlan; |
a7aa2d3c | 495 | cfm->ccm_pcp = s->ccm_pcp & (VLAN_PCP_MASK >> VLAN_PCP_SHIFT); |
de724029 EJ |
496 | if (cfm->extended && interval_ms != s->interval) { |
497 | interval = 0; | |
498 | interval_ms = MIN(s->interval, UINT16_MAX); | |
499 | } | |
9aa952b2 | 500 | |
de724029 | 501 | if (interval != cfm->ccm_interval || interval_ms != cfm->ccm_interval_ms) { |
a5610457 | 502 | cfm->ccm_interval = interval; |
de724029 | 503 | cfm->ccm_interval_ms = interval_ms; |
b31bcf60 | 504 | |
a5610457 EJ |
505 | timer_set_expired(&cfm->tx_timer); |
506 | timer_set_duration(&cfm->fault_timer, cfm_fault_interval(cfm)); | |
507 | } | |
b31bcf60 | 508 | |
a5610457 | 509 | return true; |
b31bcf60 EJ |
510 | } |
511 | ||
ef9819b5 | 512 | /* Returns true if 'cfm' should process packets from 'flow'. */ |
b31bcf60 | 513 | bool |
ef9819b5 | 514 | cfm_should_process_flow(const struct cfm *cfm, const struct flow *flow) |
b31bcf60 EJ |
515 | { |
516 | return (ntohs(flow->dl_type) == ETH_TYPE_CFM | |
ef9819b5 | 517 | && eth_addr_equals(flow->dl_dst, cfm_ccm_addr(cfm))); |
b31bcf60 EJ |
518 | } |
519 | ||
520 | /* Updates internal statistics relevant to packet 'p'. Should be called on | |
521 | * every packet whose flow returned true when passed to | |
522 | * cfm_should_process_flow. */ | |
523 | void | |
524 | cfm_process_heartbeat(struct cfm *cfm, const struct ofpbuf *p) | |
525 | { | |
526 | struct ccm *ccm; | |
0dd17bfd | 527 | struct eth_header *eth; |
b31bcf60 | 528 | |
0dd17bfd | 529 | eth = p->l2; |
ce27cbee | 530 | ccm = ofpbuf_at(p, (uint8_t *)p->l3 - (uint8_t *)p->data, CCM_ACCEPT_LEN); |
b31bcf60 EJ |
531 | |
532 | if (!ccm) { | |
32977975 EJ |
533 | VLOG_INFO_RL(&rl, "%s: Received an unparseable 802.1ag CCM heartbeat.", |
534 | cfm->name); | |
b31bcf60 EJ |
535 | return; |
536 | } | |
537 | ||
538 | if (ccm->opcode != CCM_OPCODE) { | |
32977975 EJ |
539 | VLOG_INFO_RL(&rl, "%s: Received an unsupported 802.1ag message. " |
540 | "(opcode %u)", cfm->name, ccm->opcode); | |
b31bcf60 EJ |
541 | return; |
542 | } | |
543 | ||
5e809322 | 544 | /* According to the 802.1ag specification, reception of a CCM with an |
aa7f1158 EJ |
545 | * incorrect ccm_interval, unexpected MAID, or unexpected MPID should |
546 | * trigger a fault. We ignore this requirement for several reasons. | |
5e809322 EJ |
547 | * |
548 | * Faults can cause a controller or Open vSwitch to make potentially | |
549 | * expensive changes to the network topology. It seems prudent to trigger | |
550 | * them judiciously, especially when CFM is used to check slave status of | |
551 | * bonds. Furthermore, faults can be maliciously triggered by crafting | |
2b540ecb | 552 | * unexpected CCMs. */ |
a5610457 | 553 | if (memcmp(ccm->maid, cfm->maid, sizeof ccm->maid)) { |
b9380396 | 554 | cfm->recv_fault |= CFM_FAULT_MAID; |
32977975 EJ |
555 | VLOG_WARN_RL(&rl, "%s: Received unexpected remote MAID from MAC " |
556 | ETH_ADDR_FMT, cfm->name, ETH_ADDR_ARGS(eth->eth_src)); | |
f805c4cc | 557 | } else { |
de724029 EJ |
558 | uint8_t ccm_interval = ccm->flags & 0x7; |
559 | bool ccm_rdi = ccm->flags & CCM_RDI_MASK; | |
560 | uint16_t ccm_interval_ms_x = ntohs(ccm->interval_ms_x); | |
561 | ||
562 | struct remote_mp *rmp; | |
348f01e3 | 563 | uint64_t ccm_mpid; |
8210113e | 564 | uint32_t ccm_seq; |
86dc6501 | 565 | bool ccm_opdown; |
2b540ecb | 566 | enum cfm_fault_reason cfm_fault = 0; |
348f01e3 | 567 | |
86dc6501 EJ |
568 | if (cfm->extended) { |
569 | ccm_mpid = ntohll(ccm->mpid64); | |
570 | ccm_opdown = ccm->opdown; | |
571 | } else { | |
572 | ccm_mpid = ntohs(ccm->mpid); | |
573 | ccm_opdown = false; | |
574 | } | |
8210113e | 575 | ccm_seq = ntohl(ccm->seq); |
b31bcf60 | 576 | |
144216a3 | 577 | if (ccm_interval != cfm->ccm_interval) { |
2b540ecb MM |
578 | cfm_fault |= CFM_FAULT_INTERVAL; |
579 | VLOG_WARN_RL(&rl, "%s: received a CCM with an unexpected interval" | |
348f01e3 | 580 | " (%"PRIu8") from RMP %"PRIu64, cfm->name, |
144216a3 EJ |
581 | ccm_interval, ccm_mpid); |
582 | } | |
b31bcf60 | 583 | |
de724029 EJ |
584 | if (cfm->extended && ccm_interval == 0 |
585 | && ccm_interval_ms_x != cfm->ccm_interval_ms) { | |
2b540ecb MM |
586 | cfm_fault |= CFM_FAULT_INTERVAL; |
587 | VLOG_WARN_RL(&rl, "%s: received a CCM with an unexpected extended" | |
348f01e3 | 588 | " interval (%"PRIu16"ms) from RMP %"PRIu64, cfm->name, |
de724029 EJ |
589 | ccm_interval_ms_x, ccm_mpid); |
590 | } | |
591 | ||
144216a3 | 592 | rmp = lookup_remote_mp(cfm, ccm_mpid); |
86dc6501 EJ |
593 | if (!rmp) { |
594 | if (hmap_count(&cfm->remote_mps) < CFM_MAX_RMPS) { | |
8210113e | 595 | rmp = xzalloc(sizeof *rmp); |
86dc6501 EJ |
596 | hmap_insert(&cfm->remote_mps, &rmp->node, hash_mpid(ccm_mpid)); |
597 | } else { | |
2b540ecb | 598 | cfm_fault |= CFM_FAULT_OVERFLOW; |
86dc6501 EJ |
599 | VLOG_WARN_RL(&rl, |
600 | "%s: dropped CCM with MPID %"PRIu64" from MAC " | |
601 | ETH_ADDR_FMT, cfm->name, ccm_mpid, | |
602 | ETH_ADDR_ARGS(eth->eth_src)); | |
603 | } | |
604 | } | |
605 | ||
2b540ecb MM |
606 | if (ccm_rdi) { |
607 | cfm_fault |= CFM_FAULT_RDI; | |
608 | VLOG_DBG("%s: RDI bit flagged from RMP %"PRIu64, cfm->name, | |
609 | rmp->mpid); | |
610 | } | |
611 | ||
8210113e EJ |
612 | VLOG_DBG("%s: received CCM (seq %"PRIu32") (mpid %"PRIu64")" |
613 | " (interval %"PRIu8") (RDI %s)", cfm->name, ccm_seq, | |
614 | ccm_mpid, ccm_interval, ccm_rdi ? "true" : "false"); | |
615 | ||
0dd17bfd | 616 | if (rmp) { |
2b540ecb MM |
617 | if (rmp->mpid == cfm->mpid) { |
618 | cfm_fault |= CFM_FAULT_LOOPBACK; | |
619 | VLOG_WARN_RL(&rl,"%s: received CCM with local MPID" | |
620 | " %"PRIu64, cfm->name, rmp->mpid); | |
621 | } | |
622 | ||
8210113e | 623 | if (rmp->seq && ccm_seq != (rmp->seq + 1)) { |
2b540ecb | 624 | cfm_fault |= CFM_FAULT_SEQUENCE; |
8210113e EJ |
625 | VLOG_WARN_RL(&rl, "%s: (mpid %"PRIu64") detected sequence" |
626 | " numbers which indicate possible connectivity" | |
627 | " problems (previous %"PRIu32") (current %"PRIu32 | |
628 | ")", cfm->name, ccm_mpid, rmp->seq, ccm_seq); | |
629 | } | |
630 | ||
144216a3 | 631 | rmp->mpid = ccm_mpid; |
2b540ecb | 632 | if (!cfm_fault) { |
3967a833 MM |
633 | rmp->num_health_ccm++; |
634 | } | |
2b540ecb MM |
635 | rmp->recv = true; |
636 | cfm->recv_fault |= cfm_fault; | |
8210113e | 637 | rmp->seq = ccm_seq; |
86dc6501 | 638 | rmp->opup = !ccm_opdown; |
f805c4cc | 639 | } |
b31bcf60 | 640 | } |
b31bcf60 | 641 | } |
20c8e971 | 642 | |
b9380396 EJ |
643 | /* Gets the fault status of 'cfm'. Returns a bit mask of 'cfm_fault_reason's |
644 | * indicating the cause of the connectivity fault, or zero if there is no | |
645 | * fault. */ | |
646 | int | |
a5610457 EJ |
647 | cfm_get_fault(const struct cfm *cfm) |
648 | { | |
d7243b93 | 649 | if (cfm->fault_override >= 0) { |
b9380396 | 650 | return cfm->fault_override ? CFM_FAULT_OVERRIDE : 0; |
d7243b93 | 651 | } |
a5610457 EJ |
652 | return cfm->fault; |
653 | } | |
654 | ||
3967a833 MM |
655 | /* Gets the health of 'cfm'. Returns an integer between 0 and 100 indicating |
656 | * the health of the link as a percentage of ccm frames received in | |
657 | * CFM_HEALTH_INTERVAL * 'fault_interval' if there is only 1 remote_mpid, | |
658 | * returns 0 if there are no remote_mpids, and returns -1 if there are more | |
659 | * than 1 remote_mpids. */ | |
660 | int | |
661 | cfm_get_health(const struct cfm *cfm) | |
662 | { | |
663 | return cfm->health; | |
664 | } | |
665 | ||
86dc6501 EJ |
666 | /* Gets the operational state of 'cfm'. 'cfm' is considered operationally down |
667 | * if it has received a CCM with the operationally down bit set from any of its | |
668 | * remote maintenance points. Returns true if 'cfm' is operationally up. False | |
669 | * otherwise. */ | |
670 | bool | |
671 | cfm_get_opup(const struct cfm *cfm) | |
672 | { | |
673 | return cfm->remote_opup; | |
674 | } | |
675 | ||
1de11730 EJ |
676 | /* Populates 'rmps' with an array of remote maintenance points reachable by |
677 | * 'cfm'. The number of remote maintenance points is written to 'n_rmps'. | |
678 | * 'cfm' retains ownership of the array written to 'rmps' */ | |
679 | void | |
680 | cfm_get_remote_mpids(const struct cfm *cfm, const uint64_t **rmps, | |
681 | size_t *n_rmps) | |
682 | { | |
683 | *rmps = cfm->rmps_array; | |
684 | *n_rmps = cfm->rmps_array_len; | |
685 | } | |
686 | ||
a5610457 | 687 | static struct cfm * |
9ac3fce4 | 688 | cfm_find(const char *name) |
20c8e971 | 689 | { |
a5610457 | 690 | struct cfm *cfm; |
9ac3fce4 | 691 | |
6f629657 EJ |
692 | HMAP_FOR_EACH_WITH_HASH (cfm, hmap_node, hash_string(name, 0), &all_cfms) { |
693 | if (!strcmp(cfm->name, name)) { | |
a5610457 | 694 | return cfm; |
9ac3fce4 EJ |
695 | } |
696 | } | |
697 | return NULL; | |
698 | } | |
699 | ||
700 | static void | |
ae75dae3 | 701 | cfm_print_details(struct ds *ds, const struct cfm *cfm) |
9ac3fce4 | 702 | { |
20c8e971 | 703 | struct remote_mp *rmp; |
906bb3d0 | 704 | int fault; |
20c8e971 | 705 | |
ae75dae3 | 706 | ds_put_format(ds, "---- %s ----\n", cfm->name); |
b9380396 | 707 | ds_put_format(ds, "MPID %"PRIu64":%s%s\n", cfm->mpid, |
95b0fad8 | 708 | cfm->extended ? " extended" : "", |
b9380396 EJ |
709 | cfm->fault_override >= 0 ? " fault_override" : ""); |
710 | ||
906bb3d0 BP |
711 | fault = cfm_get_fault(cfm); |
712 | if (fault) { | |
713 | ds_put_cstr(ds, "\tfault:"); | |
714 | ds_put_cfm_fault(ds, fault, fault); | |
b9380396 EJ |
715 | ds_put_cstr(ds, "\n"); |
716 | } | |
9ac3fce4 | 717 | |
3967a833 MM |
718 | if (cfm->health == -1) { |
719 | ds_put_format(ds, "\taverage health: undefined\n"); | |
720 | } else { | |
721 | ds_put_format(ds, "\taverage health: %d\n", cfm->health); | |
722 | } | |
86dc6501 EJ |
723 | ds_put_format(ds, "\topstate: %s\n", cfm->opup ? "up" : "down"); |
724 | ds_put_format(ds, "\tremote_opstate: %s\n", | |
725 | cfm->remote_opup ? "up" : "down"); | |
ae75dae3 JP |
726 | ds_put_format(ds, "\tinterval: %dms\n", cfm->ccm_interval_ms); |
727 | ds_put_format(ds, "\tnext CCM tx: %lldms\n", | |
a5610457 | 728 | timer_msecs_until_expired(&cfm->tx_timer)); |
ae75dae3 | 729 | ds_put_format(ds, "\tnext fault check: %lldms\n", |
a5610457 | 730 | timer_msecs_until_expired(&cfm->fault_timer)); |
20c8e971 | 731 | |
a5610457 | 732 | HMAP_FOR_EACH (rmp, node, &cfm->remote_mps) { |
2b540ecb | 733 | ds_put_format(ds, "Remote MPID %"PRIu64"\n", rmp->mpid); |
7593daa2 | 734 | ds_put_format(ds, "\trecv since check: %s\n", |
dd986e09 | 735 | rmp->recv ? "true" : "false"); |
86dc6501 | 736 | ds_put_format(ds, "\topstate: %s\n", rmp->opup? "up" : "down"); |
20c8e971 | 737 | } |
ae75dae3 JP |
738 | } |
739 | ||
740 | static void | |
0e15264f BP |
741 | cfm_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], |
742 | void *aux OVS_UNUSED) | |
ae75dae3 JP |
743 | { |
744 | struct ds ds = DS_EMPTY_INITIALIZER; | |
745 | const struct cfm *cfm; | |
746 | ||
0e15264f BP |
747 | if (argc > 1) { |
748 | cfm = cfm_find(argv[1]); | |
ae75dae3 | 749 | if (!cfm) { |
bde9f75d | 750 | unixctl_command_reply_error(conn, "no such CFM object"); |
ae75dae3 JP |
751 | return; |
752 | } | |
753 | cfm_print_details(&ds, cfm); | |
754 | } else { | |
755 | HMAP_FOR_EACH (cfm, hmap_node, &all_cfms) { | |
756 | cfm_print_details(&ds, cfm); | |
757 | } | |
758 | } | |
9ac3fce4 | 759 | |
bde9f75d | 760 | unixctl_command_reply(conn, ds_cstr(&ds)); |
9ac3fce4 | 761 | ds_destroy(&ds); |
20c8e971 | 762 | } |
d7243b93 EJ |
763 | |
764 | static void | |
765 | cfm_unixctl_set_fault(struct unixctl_conn *conn, int argc, const char *argv[], | |
766 | void *aux OVS_UNUSED) | |
767 | { | |
768 | const char *fault_str = argv[argc - 1]; | |
769 | int fault_override; | |
770 | struct cfm *cfm; | |
771 | ||
772 | if (!strcasecmp("true", fault_str)) { | |
773 | fault_override = 1; | |
774 | } else if (!strcasecmp("false", fault_str)) { | |
775 | fault_override = 0; | |
776 | } else if (!strcasecmp("normal", fault_str)) { | |
777 | fault_override = -1; | |
778 | } else { | |
bde9f75d | 779 | unixctl_command_reply_error(conn, "unknown fault string"); |
d7243b93 EJ |
780 | return; |
781 | } | |
782 | ||
783 | if (argc > 2) { | |
784 | cfm = cfm_find(argv[1]); | |
785 | if (!cfm) { | |
bde9f75d | 786 | unixctl_command_reply_error(conn, "no such CFM object"); |
d7243b93 EJ |
787 | return; |
788 | } | |
789 | cfm->fault_override = fault_override; | |
790 | } else { | |
791 | HMAP_FOR_EACH (cfm, hmap_node, &all_cfms) { | |
792 | cfm->fault_override = fault_override; | |
793 | } | |
794 | } | |
795 | ||
bde9f75d | 796 | unixctl_command_reply(conn, "OK"); |
d7243b93 | 797 | } |