]>
Commit | Line | Data |
---|---|---|
ccc09689 EJ |
1 | /* Copyright (c) 2013 Nicira, Inc. |
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 | #include <config.h> | |
16 | #include "bfd.h" | |
17 | ||
18 | #include <arpa/inet.h> | |
2fbf137d | 19 | #include <netinet/in_systm.h> |
b644259f | 20 | #include <netinet/ip.h> |
ccc09689 | 21 | |
f645ee9c | 22 | #include "byte-order.h" |
ccc09689 EJ |
23 | #include "csum.h" |
24 | #include "dpif.h" | |
25 | #include "dynamic-string.h" | |
26 | #include "flow.h" | |
27 | #include "hash.h" | |
28 | #include "hmap.h" | |
29 | #include "list.h" | |
30 | #include "netlink.h" | |
31 | #include "odp-util.h" | |
32 | #include "ofpbuf.h" | |
26131299 | 33 | #include "ovs-thread.h" |
ccc09689 EJ |
34 | #include "openvswitch/types.h" |
35 | #include "packets.h" | |
36 | #include "poll-loop.h" | |
37 | #include "random.h" | |
38 | #include "smap.h" | |
39 | #include "timeval.h" | |
40 | #include "unixctl.h" | |
41 | #include "util.h" | |
42 | #include "vlog.h" | |
43 | ||
44 | VLOG_DEFINE_THIS_MODULE(bfd); | |
45 | ||
46 | /* XXX Finish BFD. | |
47 | * | |
48 | * The goal of this module is to replace CFM with something both more flexible | |
49 | * and standards compliant. In service of this goal, the following needs to be | |
50 | * done. | |
51 | * | |
52 | * - Compliance | |
53 | * * Implement Demand mode. | |
54 | * * Go through the RFC line by line and verify we comply. | |
55 | * * Test against a hardware implementation. Preferably a popular one. | |
56 | * * Delete BFD packets with nw_ttl != 255 in the datapath to prevent DOS | |
57 | * attacks. | |
58 | * | |
59 | * - Unit tests. | |
60 | * | |
b644259f | 61 | * - Set TOS/PCP on the outer tunnel header when encapped. |
ccc09689 | 62 | * |
ccc09689 EJ |
63 | * - Sending BFD messages should be in its own thread/process. |
64 | * | |
65 | * - Scale testing. How does it operate when there are large number of bfd | |
66 | * sessions? Do we ever have random flaps? What's the CPU utilization? | |
67 | * | |
68 | * - Rely on data traffic for liveness by using BFD demand mode. | |
69 | * If we're receiving traffic on a port, we can safely assume it's up (modulo | |
70 | * unidrectional failures). BFD has a demand mode in which it can stay quiet | |
71 | * unless it feels the need to check the status of the port. Using this, we | |
72 | * can implement a strategy in which BFD only sends control messages on dark | |
73 | * interfaces. | |
74 | * | |
75 | * - Depending on how one interprets the spec, it appears that a BFD session | |
76 | * can never change bfd.LocalDiag to "No Diagnostic". We should verify that | |
77 | * this is what hardware implementations actually do. Seems like "No | |
78 | * Diagnostic" should be set once a BFD session state goes UP. */ | |
79 | ||
80 | #define BFD_VERSION 1 | |
81 | ||
82 | enum flags { | |
83 | FLAG_MULTIPOINT = 1 << 0, | |
84 | FLAG_DEMAND = 1 << 1, | |
85 | FLAG_AUTH = 1 << 2, | |
86 | FLAG_CTL = 1 << 3, | |
87 | FLAG_FINAL = 1 << 4, | |
88 | FLAG_POLL = 1 << 5 | |
89 | }; | |
90 | ||
91 | enum state { | |
92 | STATE_ADMIN_DOWN = 0 << 6, | |
93 | STATE_DOWN = 1 << 6, | |
94 | STATE_INIT = 2 << 6, | |
95 | STATE_UP = 3 << 6 | |
96 | }; | |
97 | ||
98 | enum diag { | |
99 | DIAG_NONE = 0, /* No Diagnostic. */ | |
100 | DIAG_EXPIRED = 1, /* Control Detection Time Expired. */ | |
101 | DIAG_ECHO_FAILED = 2, /* Echo Function Failed. */ | |
102 | DIAG_RMT_DOWN = 3, /* Neighbor Signaled Session Down. */ | |
103 | DIAG_FWD_RESET = 4, /* Forwarding Plane Reset. */ | |
104 | DIAG_PATH_DOWN = 5, /* Path Down. */ | |
105 | DIAG_CPATH_DOWN = 6, /* Concatenated Path Down. */ | |
106 | DIAG_ADMIN_DOWN = 7, /* Administratively Down. */ | |
107 | DIAG_RCPATH_DOWN = 8 /* Reverse Concatenated Path Down. */ | |
108 | }; | |
109 | ||
110 | /* RFC 5880 Section 4.1 | |
111 | * 0 1 2 3 | |
112 | * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 | |
113 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
114 | * |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | | |
115 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
116 | * | My Discriminator | | |
117 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
118 | * | Your Discriminator | | |
119 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
120 | * | Desired Min TX Interval | | |
121 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
122 | * | Required Min RX Interval | | |
123 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
124 | * | Required Min Echo RX Interval | | |
125 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ | |
126 | struct msg { | |
127 | uint8_t vers_diag; /* Version and diagnostic. */ | |
128 | uint8_t flags; /* 2bit State field followed by flags. */ | |
129 | uint8_t mult; /* Fault detection multiplier. */ | |
130 | uint8_t length; /* Length of this BFD message. */ | |
131 | ovs_be32 my_disc; /* My discriminator. */ | |
132 | ovs_be32 your_disc; /* Your discriminator. */ | |
133 | ovs_be32 min_tx; /* Desired minimum tx interval. */ | |
134 | ovs_be32 min_rx; /* Required minimum rx interval. */ | |
135 | ovs_be32 min_rx_echo; /* Required minimum echo rx interval. */ | |
136 | }; | |
137 | BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct msg)); | |
138 | ||
139 | #define DIAG_MASK 0x1f | |
140 | #define VERS_SHIFT 5 | |
141 | #define STATE_MASK 0xC0 | |
142 | #define FLAGS_MASK 0x3f | |
143 | ||
144 | struct bfd { | |
145 | struct hmap_node node; /* In 'all_bfds'. */ | |
146 | uint32_t disc; /* bfd.LocalDiscr. Key in 'all_bfds' hmap. */ | |
147 | ||
148 | char *name; /* Name used for logging. */ | |
149 | ||
150 | bool cpath_down; /* Concatenated Path Down. */ | |
151 | uint8_t mult; /* bfd.DetectMult. */ | |
152 | ||
153 | enum state state; /* bfd.SessionState. */ | |
154 | enum state rmt_state; /* bfd.RemoteSessionState. */ | |
155 | ||
156 | enum diag diag; /* bfd.LocalDiag. */ | |
157 | enum diag rmt_diag; /* Remote diagnostic. */ | |
158 | ||
159 | enum flags flags; /* Flags sent on messages. */ | |
160 | enum flags rmt_flags; /* Flags last received. */ | |
161 | ||
162 | uint32_t rmt_disc; /* bfd.RemoteDiscr. */ | |
163 | ||
de8d2ef9 GS |
164 | uint8_t eth_dst[ETH_ADDR_LEN];/* Ethernet destination address. */ |
165 | bool eth_dst_set; /* 'eth_dst' set through database. */ | |
166 | ||
ccc09689 EJ |
167 | uint16_t udp_src; /* UDP source port. */ |
168 | ||
169 | /* All timers in milliseconds. */ | |
170 | long long int rmt_min_rx; /* bfd.RemoteMinRxInterval. */ | |
171 | long long int rmt_min_tx; /* Remote minimum TX interval. */ | |
172 | ||
173 | long long int cfg_min_tx; /* Configured minimum TX rate. */ | |
174 | long long int cfg_min_rx; /* Configured required minimum RX rate. */ | |
175 | long long int poll_min_tx; /* Min TX negotating in a poll sequence. */ | |
176 | long long int poll_min_rx; /* Min RX negotating in a poll sequence. */ | |
177 | long long int min_tx; /* bfd.DesiredMinTxInterval. */ | |
178 | long long int min_rx; /* bfd.RequiredMinRxInterval. */ | |
179 | ||
180 | long long int last_tx; /* Last TX time. */ | |
181 | long long int next_tx; /* Next TX time. */ | |
182 | long long int detect_time; /* RFC 5880 6.8.4 Detection time. */ | |
92cfab82 | 183 | |
91aaf124 | 184 | int forwarding_override; /* Manual override of 'forwarding' status. */ |
26131299 EJ |
185 | |
186 | atomic_bool check_tnl_key; /* Verify tunnel key of inbound packets? */ | |
187 | atomic_int ref_cnt; | |
ccc09689 EJ |
188 | }; |
189 | ||
26131299 EJ |
190 | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
191 | static struct hmap all_bfds__ = HMAP_INITIALIZER(&all_bfds__); | |
192 | static struct hmap *const all_bfds OVS_GUARDED_BY(mutex) = &all_bfds__; | |
193 | ||
bd3950dd | 194 | static bool bfd_forwarding__(const struct bfd *) OVS_REQUIRES(mutex); |
344e21d4 AW |
195 | static bool bfd_in_poll(const struct bfd *) OVS_REQUIRES(mutex); |
196 | static void bfd_poll(struct bfd *bfd) OVS_REQUIRES(mutex); | |
197 | static const char *bfd_diag_str(enum diag) OVS_REQUIRES(mutex); | |
198 | static const char *bfd_state_str(enum state) OVS_REQUIRES(mutex); | |
199 | static long long int bfd_min_tx(const struct bfd *) OVS_REQUIRES(mutex); | |
26131299 | 200 | static long long int bfd_tx_interval(const struct bfd *) |
344e21d4 | 201 | OVS_REQUIRES(mutex); |
26131299 | 202 | static long long int bfd_rx_interval(const struct bfd *) |
344e21d4 AW |
203 | OVS_REQUIRES(mutex); |
204 | static void bfd_set_next_tx(struct bfd *) OVS_REQUIRES(mutex); | |
26131299 | 205 | static void bfd_set_state(struct bfd *, enum state, enum diag) |
344e21d4 AW |
206 | OVS_REQUIRES(mutex); |
207 | static uint32_t generate_discriminator(void) OVS_REQUIRES(mutex); | |
26131299 | 208 | static void bfd_put_details(struct ds *, const struct bfd *) |
344e21d4 | 209 | OVS_REQUIRES(mutex); |
ccc09689 EJ |
210 | static void bfd_unixctl_show(struct unixctl_conn *, int argc, |
211 | const char *argv[], void *aux OVS_UNUSED); | |
91aaf124 PR |
212 | static void bfd_unixctl_set_forwarding_override(struct unixctl_conn *, |
213 | int argc, const char *argv[], | |
214 | void *aux OVS_UNUSED); | |
ccc09689 | 215 | static void log_msg(enum vlog_level, const struct msg *, const char *message, |
344e21d4 | 216 | const struct bfd *) OVS_REQUIRES(mutex); |
ccc09689 EJ |
217 | |
218 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 20); | |
ccc09689 EJ |
219 | |
220 | /* Returns true if the interface on which 'bfd' is running may be used to | |
221 | * forward traffic according to the BFD session state. */ | |
222 | bool | |
26131299 | 223 | bfd_forwarding(const struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 224 | { |
26131299 | 225 | bool ret; |
91aaf124 | 226 | |
26131299 EJ |
227 | ovs_mutex_lock(&mutex); |
228 | ret = bfd_forwarding__(bfd); | |
229 | ovs_mutex_unlock(&mutex); | |
230 | return ret; | |
ccc09689 EJ |
231 | } |
232 | ||
233 | /* Returns a 'smap' of key value pairs representing the status of 'bfd' | |
234 | * intended for the OVS database. */ | |
235 | void | |
236 | bfd_get_status(const struct bfd *bfd, struct smap *smap) | |
26131299 | 237 | OVS_EXCLUDED(mutex) |
ccc09689 | 238 | { |
26131299 EJ |
239 | ovs_mutex_lock(&mutex); |
240 | smap_add(smap, "forwarding", bfd_forwarding__(bfd)? "true" : "false"); | |
ccc09689 EJ |
241 | smap_add(smap, "state", bfd_state_str(bfd->state)); |
242 | smap_add(smap, "diagnostic", bfd_diag_str(bfd->diag)); | |
243 | ||
244 | if (bfd->state != STATE_DOWN) { | |
245 | smap_add(smap, "remote_state", bfd_state_str(bfd->rmt_state)); | |
246 | smap_add(smap, "remote_diagnostic", bfd_diag_str(bfd->rmt_diag)); | |
247 | } | |
26131299 | 248 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
249 | } |
250 | ||
251 | /* Initializes, destroys, or reconfigures the BFD session 'bfd' (named 'name'), | |
252 | * according to the database configuration contained in 'cfg'. Takes ownership | |
253 | * of 'bfd', which may be NULL. Returns a BFD object which may be used as a | |
8aee94b6 PR |
254 | * handle for the session, or NULL if BFD is not enabled according to 'cfg'. |
255 | * Also returns NULL if cfg is NULL. */ | |
ccc09689 | 256 | struct bfd * |
26131299 EJ |
257 | bfd_configure(struct bfd *bfd, const char *name, const struct smap *cfg) |
258 | OVS_EXCLUDED(mutex) | |
ccc09689 | 259 | { |
26131299 EJ |
260 | static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; |
261 | static atomic_uint16_t udp_src = ATOMIC_VAR_INIT(0); | |
ccc09689 EJ |
262 | |
263 | long long int min_tx, min_rx; | |
264 | bool cpath_down; | |
de8d2ef9 GS |
265 | const char *hwaddr; |
266 | uint8_t ea[ETH_ADDR_LEN]; | |
ccc09689 | 267 | |
26131299 | 268 | if (ovsthread_once_start(&once)) { |
ccc09689 EJ |
269 | unixctl_command_register("bfd/show", "[interface]", 0, 1, |
270 | bfd_unixctl_show, NULL); | |
91aaf124 PR |
271 | unixctl_command_register("bfd/set-forwarding", |
272 | "[interface] normal|false|true", 1, 2, | |
273 | bfd_unixctl_set_forwarding_override, NULL); | |
26131299 | 274 | ovsthread_once_done(&once); |
ccc09689 EJ |
275 | } |
276 | ||
8aee94b6 | 277 | if (!cfg || !smap_get_bool(cfg, "enable", false)) { |
92cfab82 | 278 | bfd_unref(bfd); |
ccc09689 EJ |
279 | return NULL; |
280 | } | |
281 | ||
26131299 | 282 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
283 | if (!bfd) { |
284 | bfd = xzalloc(sizeof *bfd); | |
285 | bfd->name = xstrdup(name); | |
91aaf124 | 286 | bfd->forwarding_override = -1; |
ccc09689 | 287 | bfd->disc = generate_discriminator(); |
26131299 | 288 | hmap_insert(all_bfds, &bfd->node, bfd->disc); |
ccc09689 EJ |
289 | |
290 | bfd->diag = DIAG_NONE; | |
291 | bfd->min_tx = 1000; | |
292 | bfd->mult = 3; | |
26131299 | 293 | atomic_init(&bfd->ref_cnt, 1); |
ccc09689 EJ |
294 | |
295 | /* RFC 5881 section 4 | |
296 | * The source port MUST be in the range 49152 through 65535. The same | |
297 | * UDP source port number MUST be used for all BFD Control packets | |
298 | * associated with a particular session. The source port number SHOULD | |
299 | * be unique among all BFD sessions on the system. */ | |
26131299 EJ |
300 | atomic_add(&udp_src, 1, &bfd->udp_src); |
301 | bfd->udp_src = (bfd->udp_src % 16384) + 49152; | |
ccc09689 EJ |
302 | |
303 | bfd_set_state(bfd, STATE_DOWN, DIAG_NONE); | |
de8d2ef9 GS |
304 | |
305 | memcpy(bfd->eth_dst, eth_addr_bfd, ETH_ADDR_LEN); | |
ccc09689 EJ |
306 | } |
307 | ||
26131299 EJ |
308 | atomic_store(&bfd->check_tnl_key, |
309 | smap_get_bool(cfg, "check_tnl_key", false)); | |
ccc09689 EJ |
310 | min_tx = smap_get_int(cfg, "min_tx", 100); |
311 | min_tx = MAX(min_tx, 100); | |
312 | if (bfd->cfg_min_tx != min_tx) { | |
313 | bfd->cfg_min_tx = min_tx; | |
314 | if (bfd->state != STATE_UP | |
315 | || (!bfd_in_poll(bfd) && bfd->cfg_min_tx < bfd->min_tx)) { | |
316 | bfd->min_tx = bfd->cfg_min_tx; | |
317 | } | |
318 | bfd_poll(bfd); | |
319 | } | |
320 | ||
321 | min_rx = smap_get_int(cfg, "min_rx", 1000); | |
322 | min_rx = MAX(min_rx, 100); | |
323 | if (bfd->cfg_min_rx != min_rx) { | |
324 | bfd->cfg_min_rx = min_rx; | |
325 | if (bfd->state != STATE_UP | |
326 | || (!bfd_in_poll(bfd) && bfd->cfg_min_rx > bfd->min_rx)) { | |
327 | bfd->min_rx = bfd->cfg_min_rx; | |
328 | } | |
329 | bfd_poll(bfd); | |
330 | } | |
331 | ||
332 | cpath_down = smap_get_bool(cfg, "cpath_down", false); | |
333 | if (bfd->cpath_down != cpath_down) { | |
334 | bfd->cpath_down = cpath_down; | |
335 | if (bfd->diag == DIAG_NONE || bfd->diag == DIAG_CPATH_DOWN) { | |
336 | bfd_set_state(bfd, bfd->state, DIAG_NONE); | |
337 | } | |
338 | bfd_poll(bfd); | |
339 | } | |
de8d2ef9 GS |
340 | |
341 | hwaddr = smap_get(cfg, "bfd_dst_mac"); | |
342 | if (hwaddr && eth_addr_from_string(hwaddr, ea) && !eth_addr_is_zero(ea)) { | |
343 | memcpy(bfd->eth_dst, ea, ETH_ADDR_LEN); | |
344 | bfd->eth_dst_set = true; | |
345 | } else if (bfd->eth_dst_set) { | |
346 | memcpy(bfd->eth_dst, eth_addr_bfd, ETH_ADDR_LEN); | |
347 | bfd->eth_dst_set = false; | |
348 | } | |
349 | ||
26131299 | 350 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
351 | return bfd; |
352 | } | |
353 | ||
92cfab82 EJ |
354 | struct bfd * |
355 | bfd_ref(const struct bfd *bfd_) | |
356 | { | |
357 | struct bfd *bfd = CONST_CAST(struct bfd *, bfd_); | |
358 | if (bfd) { | |
26131299 EJ |
359 | int orig; |
360 | atomic_add(&bfd->ref_cnt, 1, &orig); | |
361 | ovs_assert(orig > 0); | |
92cfab82 EJ |
362 | } |
363 | return bfd; | |
364 | } | |
365 | ||
366 | void | |
26131299 | 367 | bfd_unref(struct bfd *bfd) OVS_EXCLUDED(mutex) |
92cfab82 EJ |
368 | { |
369 | if (bfd) { | |
26131299 EJ |
370 | int orig; |
371 | ||
372 | atomic_sub(&bfd->ref_cnt, 1, &orig); | |
373 | ovs_assert(orig > 0); | |
374 | if (orig == 1) { | |
375 | ovs_mutex_lock(&mutex); | |
376 | hmap_remove(all_bfds, &bfd->node); | |
92cfab82 EJ |
377 | free(bfd->name); |
378 | free(bfd); | |
26131299 | 379 | ovs_mutex_unlock(&mutex); |
92cfab82 EJ |
380 | } |
381 | } | |
382 | } | |
383 | ||
ccc09689 | 384 | void |
26131299 | 385 | bfd_wait(const struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 386 | { |
26131299 | 387 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
388 | if (bfd->flags & FLAG_FINAL) { |
389 | poll_immediate_wake(); | |
390 | } | |
391 | ||
392 | poll_timer_wait_until(bfd->next_tx); | |
393 | if (bfd->state > STATE_DOWN) { | |
394 | poll_timer_wait_until(bfd->detect_time); | |
395 | } | |
26131299 | 396 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
397 | } |
398 | ||
399 | void | |
26131299 | 400 | bfd_run(struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 401 | { |
26131299 | 402 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
403 | if (bfd->state > STATE_DOWN && time_msec() >= bfd->detect_time) { |
404 | bfd_set_state(bfd, STATE_DOWN, DIAG_EXPIRED); | |
405 | } | |
406 | ||
407 | if (bfd->min_tx != bfd->cfg_min_tx || bfd->min_rx != bfd->cfg_min_rx) { | |
408 | bfd_poll(bfd); | |
409 | } | |
26131299 | 410 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
411 | } |
412 | ||
413 | bool | |
26131299 | 414 | bfd_should_send_packet(const struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 415 | { |
26131299 EJ |
416 | bool ret; |
417 | ovs_mutex_lock(&mutex); | |
418 | ret = bfd->flags & FLAG_FINAL || time_msec() >= bfd->next_tx; | |
419 | ovs_mutex_unlock(&mutex); | |
420 | return ret; | |
ccc09689 EJ |
421 | } |
422 | ||
423 | void | |
424 | bfd_put_packet(struct bfd *bfd, struct ofpbuf *p, | |
26131299 | 425 | uint8_t eth_src[ETH_ADDR_LEN]) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
426 | { |
427 | long long int min_tx, min_rx; | |
428 | struct udp_header *udp; | |
429 | struct eth_header *eth; | |
430 | struct ip_header *ip; | |
431 | struct msg *msg; | |
432 | ||
26131299 | 433 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
434 | if (bfd->next_tx) { |
435 | long long int delay = time_msec() - bfd->next_tx; | |
436 | long long int interval = bfd_tx_interval(bfd); | |
437 | if (delay > interval * 3 / 2) { | |
6a690106 | 438 | VLOG_INFO("%s: long delay of %lldms (expected %lldms) sending BFD" |
ccc09689 EJ |
439 | " control message", bfd->name, delay, interval); |
440 | } | |
441 | } | |
442 | ||
443 | /* RFC 5880 Section 6.5 | |
444 | * A BFD Control packet MUST NOT have both the Poll (P) and Final (F) bits | |
445 | * set. */ | |
446 | ovs_assert(!(bfd->flags & FLAG_POLL) || !(bfd->flags & FLAG_FINAL)); | |
447 | ||
448 | ofpbuf_reserve(p, 2); /* Properly align after the ethernet header. */ | |
449 | eth = ofpbuf_put_uninit(p, sizeof *eth); | |
ccc09689 | 450 | memcpy(eth->eth_src, eth_src, ETH_ADDR_LEN); |
de8d2ef9 | 451 | memcpy(eth->eth_dst, bfd->eth_dst, ETH_ADDR_LEN); |
ccc09689 EJ |
452 | eth->eth_type = htons(ETH_TYPE_IP); |
453 | ||
454 | ip = ofpbuf_put_zeros(p, sizeof *ip); | |
455 | ip->ip_ihl_ver = IP_IHL_VER(5, 4); | |
456 | ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg); | |
b644259f PR |
457 | ip->ip_ttl = MAXTTL; |
458 | ip->ip_tos = IPTOS_LOWDELAY | IPTOS_THROUGHPUT; | |
ccc09689 EJ |
459 | ip->ip_proto = IPPROTO_UDP; |
460 | ip->ip_src = htonl(0xA9FE0100); /* 169.254.1.0 Link Local. */ | |
461 | ip->ip_dst = htonl(0xA9FE0101); /* 169.254.1.1 Link Local. */ | |
462 | ip->ip_csum = csum(ip, sizeof *ip); | |
463 | ||
464 | udp = ofpbuf_put_zeros(p, sizeof *udp); | |
465 | udp->udp_src = htons(bfd->udp_src); | |
466 | udp->udp_dst = htons(BFD_DEST_PORT); | |
467 | udp->udp_len = htons(sizeof *udp + sizeof *msg); | |
468 | ||
469 | msg = ofpbuf_put_uninit(p, sizeof *msg); | |
470 | msg->vers_diag = (BFD_VERSION << 5) | bfd->diag; | |
471 | msg->flags = (bfd->state & STATE_MASK) | bfd->flags; | |
472 | ||
473 | msg->mult = bfd->mult; | |
474 | msg->length = BFD_PACKET_LEN; | |
475 | msg->my_disc = htonl(bfd->disc); | |
476 | msg->your_disc = htonl(bfd->rmt_disc); | |
477 | msg->min_rx_echo = htonl(0); | |
478 | ||
479 | if (bfd_in_poll(bfd)) { | |
480 | min_tx = bfd->poll_min_tx; | |
481 | min_rx = bfd->poll_min_rx; | |
482 | } else { | |
483 | min_tx = bfd_min_tx(bfd); | |
484 | min_rx = bfd->min_rx; | |
485 | } | |
486 | ||
487 | msg->min_tx = htonl(min_tx * 1000); | |
488 | msg->min_rx = htonl(min_rx * 1000); | |
489 | ||
490 | bfd->flags &= ~FLAG_FINAL; | |
491 | ||
492 | log_msg(VLL_DBG, msg, "Sending BFD Message", bfd); | |
493 | ||
494 | bfd->last_tx = time_msec(); | |
495 | bfd_set_next_tx(bfd); | |
26131299 | 496 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
497 | } |
498 | ||
499 | bool | |
fab52e16 PR |
500 | bfd_should_process_flow(const struct bfd *bfd, const struct flow *flow, |
501 | struct flow_wildcards *wc) | |
ccc09689 | 502 | { |
26131299 | 503 | bool check_tnl_key; |
de8d2ef9 GS |
504 | memset(&wc->masks.dl_dst, 0xff, sizeof wc->masks.dl_dst); |
505 | if (bfd->eth_dst_set && memcmp(bfd->eth_dst, flow->dl_dst, ETH_ADDR_LEN)) { | |
506 | return false; | |
507 | } | |
26131299 | 508 | |
642dc74d JP |
509 | memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto); |
510 | memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst); | |
26131299 EJ |
511 | |
512 | atomic_read(&bfd->check_tnl_key, &check_tnl_key); | |
513 | if (check_tnl_key) { | |
fab52e16 PR |
514 | memset(&wc->masks.tunnel.tun_id, 0xff, sizeof wc->masks.tunnel.tun_id); |
515 | } | |
ccc09689 EJ |
516 | return (flow->dl_type == htons(ETH_TYPE_IP) |
517 | && flow->nw_proto == IPPROTO_UDP | |
de8d2ef9 | 518 | && flow->tp_dst == htons(BFD_DEST_PORT) |
26131299 | 519 | && (check_tnl_key || flow->tunnel.tun_id == htonll(0))); |
ccc09689 EJ |
520 | } |
521 | ||
522 | void | |
523 | bfd_process_packet(struct bfd *bfd, const struct flow *flow, | |
26131299 | 524 | const struct ofpbuf *p) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
525 | { |
526 | uint32_t rmt_min_rx, pkt_your_disc; | |
527 | enum state rmt_state; | |
528 | enum flags flags; | |
529 | uint8_t version; | |
530 | struct msg *msg; | |
531 | ||
532 | /* This function is designed to follow section RFC 5880 6.8.6 closely. */ | |
533 | ||
26131299 | 534 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
535 | if (flow->nw_ttl != 255) { |
536 | /* XXX Should drop in the kernel to prevent DOS. */ | |
26131299 | 537 | goto out; |
ccc09689 EJ |
538 | } |
539 | ||
540 | msg = ofpbuf_at(p, (uint8_t *)p->l7 - (uint8_t *)p->data, BFD_PACKET_LEN); | |
541 | if (!msg) { | |
542 | VLOG_INFO_RL(&rl, "%s: Received unparseable BFD control message.", | |
543 | bfd->name); | |
26131299 | 544 | goto out; |
ccc09689 EJ |
545 | } |
546 | ||
547 | /* RFC 5880 Section 6.8.6 | |
548 | * If the Length field is greater than the payload of the encapsulating | |
549 | * protocol, the packet MUST be discarded. | |
550 | * | |
551 | * Note that we make this check implicity. Above we use ofpbuf_at() to | |
552 | * ensure that there are at least BFD_PACKET_LEN bytes in the payload of | |
553 | * the encapsulating protocol. Below we require msg->length to be exactly | |
554 | * BFD_PACKET_LEN bytes. */ | |
555 | ||
556 | flags = msg->flags & FLAGS_MASK; | |
557 | rmt_state = msg->flags & STATE_MASK; | |
558 | version = msg->vers_diag >> VERS_SHIFT; | |
559 | ||
560 | log_msg(VLL_DBG, msg, "Received BFD control message", bfd); | |
561 | ||
562 | if (version != BFD_VERSION) { | |
563 | log_msg(VLL_WARN, msg, "Incorrect version", bfd); | |
26131299 | 564 | goto out; |
ccc09689 EJ |
565 | } |
566 | ||
567 | /* Technically this should happen after the length check. We don't support | |
568 | * authentication however, so it's simpler to do the check first. */ | |
569 | if (flags & FLAG_AUTH) { | |
570 | log_msg(VLL_WARN, msg, "Authenticated control message with" | |
571 | " authentication disabled", bfd); | |
26131299 | 572 | goto out; |
ccc09689 EJ |
573 | } |
574 | ||
575 | if (msg->length != BFD_PACKET_LEN) { | |
576 | log_msg(VLL_WARN, msg, "Unexpected length", bfd); | |
577 | if (msg->length < BFD_PACKET_LEN) { | |
26131299 | 578 | goto out; |
ccc09689 EJ |
579 | } |
580 | } | |
581 | ||
582 | if (!msg->mult) { | |
583 | log_msg(VLL_WARN, msg, "Zero multiplier", bfd); | |
26131299 | 584 | goto out; |
ccc09689 EJ |
585 | } |
586 | ||
587 | if (flags & FLAG_MULTIPOINT) { | |
588 | log_msg(VLL_WARN, msg, "Unsupported multipoint flag", bfd); | |
26131299 | 589 | goto out; |
ccc09689 EJ |
590 | } |
591 | ||
592 | if (!msg->my_disc) { | |
593 | log_msg(VLL_WARN, msg, "NULL my_disc", bfd); | |
26131299 | 594 | goto out; |
ccc09689 EJ |
595 | } |
596 | ||
597 | pkt_your_disc = ntohl(msg->your_disc); | |
598 | if (pkt_your_disc) { | |
599 | /* Technically, we should use the your discriminator field to figure | |
600 | * out which 'struct bfd' this packet is destined towards. That way a | |
601 | * bfd session could migrate from one interface to another | |
602 | * transparently. This doesn't fit in with the OVS structure very | |
603 | * well, so in this respect, we are not compliant. */ | |
604 | if (pkt_your_disc != bfd->disc) { | |
605 | log_msg(VLL_WARN, msg, "Incorrect your_disc", bfd); | |
26131299 | 606 | goto out; |
ccc09689 EJ |
607 | } |
608 | } else if (rmt_state > STATE_DOWN) { | |
609 | log_msg(VLL_WARN, msg, "Null your_disc", bfd); | |
26131299 | 610 | goto out; |
ccc09689 EJ |
611 | } |
612 | ||
613 | bfd->rmt_disc = ntohl(msg->my_disc); | |
614 | bfd->rmt_state = rmt_state; | |
615 | bfd->rmt_flags = flags; | |
616 | bfd->rmt_diag = msg->vers_diag & DIAG_MASK; | |
617 | ||
618 | if (flags & FLAG_FINAL && bfd_in_poll(bfd)) { | |
619 | bfd->min_tx = bfd->poll_min_tx; | |
620 | bfd->min_rx = bfd->poll_min_rx; | |
621 | bfd->flags &= ~FLAG_POLL; | |
622 | log_msg(VLL_INFO, msg, "Poll sequence terminated", bfd); | |
623 | } | |
624 | ||
625 | if (flags & FLAG_POLL) { | |
626 | /* RFC 5880 Section 6.5 | |
627 | * When the other system receives a Poll, it immediately transmits a | |
628 | * BFD Control packet with the Final (F) bit set, independent of any | |
629 | * periodic BFD Control packets it may be sending | |
630 | * (see section 6.8.7). */ | |
631 | bfd->flags &= ~FLAG_POLL; | |
632 | bfd->flags |= FLAG_FINAL; | |
633 | } | |
634 | ||
635 | rmt_min_rx = MAX(ntohl(msg->min_rx) / 1000, 1); | |
636 | if (bfd->rmt_min_rx != rmt_min_rx) { | |
637 | bfd->rmt_min_rx = rmt_min_rx; | |
638 | bfd_set_next_tx(bfd); | |
639 | log_msg(VLL_INFO, msg, "New remote min_rx", bfd); | |
640 | } | |
641 | ||
642 | bfd->rmt_min_tx = MAX(ntohl(msg->min_tx) / 1000, 1); | |
643 | bfd->detect_time = bfd_rx_interval(bfd) * bfd->mult + time_msec(); | |
644 | ||
645 | if (bfd->state == STATE_ADMIN_DOWN) { | |
646 | VLOG_DBG_RL(&rl, "Administratively down, dropping control message."); | |
26131299 | 647 | goto out; |
ccc09689 EJ |
648 | } |
649 | ||
650 | if (rmt_state == STATE_ADMIN_DOWN) { | |
651 | if (bfd->state != STATE_DOWN) { | |
652 | bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN); | |
653 | } | |
654 | } else { | |
655 | switch (bfd->state) { | |
656 | case STATE_DOWN: | |
657 | if (rmt_state == STATE_DOWN) { | |
658 | bfd_set_state(bfd, STATE_INIT, bfd->diag); | |
659 | } else if (rmt_state == STATE_INIT) { | |
660 | bfd_set_state(bfd, STATE_UP, bfd->diag); | |
661 | } | |
662 | break; | |
663 | case STATE_INIT: | |
664 | if (rmt_state > STATE_DOWN) { | |
665 | bfd_set_state(bfd, STATE_UP, bfd->diag); | |
666 | } | |
667 | break; | |
668 | case STATE_UP: | |
669 | if (rmt_state <= STATE_DOWN) { | |
670 | bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN); | |
671 | log_msg(VLL_INFO, msg, "Remote signaled STATE_DOWN", bfd); | |
672 | } | |
673 | break; | |
674 | case STATE_ADMIN_DOWN: | |
675 | default: | |
676 | NOT_REACHED(); | |
677 | } | |
678 | } | |
679 | /* XXX: RFC 5880 Section 6.8.6 Demand mode related calculations here. */ | |
26131299 EJ |
680 | |
681 | out: | |
682 | ovs_mutex_unlock(&mutex); | |
ccc09689 EJ |
683 | } |
684 | \f | |
26131299 | 685 | static bool |
bd3950dd | 686 | bfd_forwarding__(const struct bfd *bfd) OVS_REQUIRES(mutex) |
26131299 EJ |
687 | { |
688 | if (bfd->forwarding_override != -1) { | |
689 | return bfd->forwarding_override == 1; | |
690 | } | |
691 | ||
692 | return bfd->state == STATE_UP | |
693 | && bfd->rmt_diag != DIAG_PATH_DOWN | |
694 | && bfd->rmt_diag != DIAG_CPATH_DOWN | |
695 | && bfd->rmt_diag != DIAG_RCPATH_DOWN; | |
696 | } | |
697 | ||
ccc09689 EJ |
698 | /* Helpers. */ |
699 | static bool | |
bd3950dd | 700 | bfd_in_poll(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
701 | { |
702 | return (bfd->flags & FLAG_POLL) != 0; | |
703 | } | |
704 | ||
705 | static void | |
bd3950dd | 706 | bfd_poll(struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
707 | { |
708 | if (bfd->state > STATE_DOWN && !bfd_in_poll(bfd) | |
709 | && !(bfd->flags & FLAG_FINAL)) { | |
710 | bfd->poll_min_tx = bfd->cfg_min_tx; | |
711 | bfd->poll_min_rx = bfd->cfg_min_rx; | |
712 | bfd->flags |= FLAG_POLL; | |
713 | bfd->next_tx = 0; | |
714 | VLOG_INFO_RL(&rl, "%s: Initiating poll sequence", bfd->name); | |
715 | } | |
716 | } | |
717 | ||
718 | static long long int | |
bd3950dd | 719 | bfd_min_tx(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
720 | { |
721 | /* RFC 5880 Section 6.8.3 | |
722 | * When bfd.SessionState is not Up, the system MUST set | |
723 | * bfd.DesiredMinTxInterval to a value of not less than one second | |
724 | * (1,000,000 microseconds). This is intended to ensure that the | |
725 | * bandwidth consumed by BFD sessions that are not Up is negligible, | |
726 | * particularly in the case where a neighbor may not be running BFD. */ | |
727 | return (bfd->state == STATE_UP ? bfd->min_tx : MAX(bfd->min_tx, 1000)); | |
728 | } | |
729 | ||
730 | static long long int | |
bd3950dd | 731 | bfd_tx_interval(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
732 | { |
733 | long long int interval = bfd_min_tx(bfd); | |
734 | return MAX(interval, bfd->rmt_min_rx); | |
735 | } | |
736 | ||
737 | static long long int | |
bd3950dd | 738 | bfd_rx_interval(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
739 | { |
740 | return MAX(bfd->min_rx, bfd->rmt_min_tx); | |
741 | } | |
742 | ||
743 | static void | |
bd3950dd | 744 | bfd_set_next_tx(struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
745 | { |
746 | long long int interval = bfd_tx_interval(bfd); | |
747 | interval -= interval * random_range(26) / 100; | |
748 | bfd->next_tx = bfd->last_tx + interval; | |
749 | } | |
750 | ||
751 | static const char * | |
752 | bfd_flag_str(enum flags flags) | |
753 | { | |
754 | struct ds ds = DS_EMPTY_INITIALIZER; | |
755 | static char flag_str[128]; | |
756 | ||
757 | if (!flags) { | |
758 | return "none"; | |
759 | } | |
760 | ||
761 | if (flags & FLAG_MULTIPOINT) { | |
762 | ds_put_cstr(&ds, "multipoint "); | |
763 | } | |
764 | ||
765 | if (flags & FLAG_DEMAND) { | |
766 | ds_put_cstr(&ds, "demand "); | |
767 | } | |
768 | ||
769 | if (flags & FLAG_AUTH) { | |
770 | ds_put_cstr(&ds, "auth "); | |
771 | } | |
772 | ||
773 | if (flags & FLAG_CTL) { | |
774 | ds_put_cstr(&ds, "ctl "); | |
775 | } | |
776 | ||
777 | if (flags & FLAG_FINAL) { | |
778 | ds_put_cstr(&ds, "final "); | |
779 | } | |
780 | ||
781 | if (flags & FLAG_POLL) { | |
782 | ds_put_cstr(&ds, "poll "); | |
783 | } | |
784 | ||
70e575d9 AW |
785 | /* Do not copy the trailing whitespace. */ |
786 | ds_chomp(&ds, ' '); | |
ccc09689 EJ |
787 | ovs_strlcpy(flag_str, ds_cstr(&ds), sizeof flag_str); |
788 | ds_destroy(&ds); | |
789 | return flag_str; | |
790 | } | |
791 | ||
792 | static const char * | |
793 | bfd_state_str(enum state state) | |
794 | { | |
795 | switch (state) { | |
796 | case STATE_ADMIN_DOWN: return "admin_down"; | |
797 | case STATE_DOWN: return "down"; | |
798 | case STATE_INIT: return "init"; | |
799 | case STATE_UP: return "up"; | |
800 | default: return "invalid"; | |
801 | } | |
802 | } | |
803 | ||
804 | static const char * | |
805 | bfd_diag_str(enum diag diag) { | |
806 | switch (diag) { | |
807 | case DIAG_NONE: return "No Diagnostic"; | |
808 | case DIAG_EXPIRED: return "Control Detection Time Expired"; | |
809 | case DIAG_ECHO_FAILED: return "Echo Function Failed"; | |
810 | case DIAG_RMT_DOWN: return "Neighbor Signaled Session Down"; | |
811 | case DIAG_FWD_RESET: return "Forwarding Plane Reset"; | |
812 | case DIAG_PATH_DOWN: return "Path Down"; | |
813 | case DIAG_CPATH_DOWN: return "Concatenated Path Down"; | |
814 | case DIAG_ADMIN_DOWN: return "Administratively Down"; | |
815 | case DIAG_RCPATH_DOWN: return "Reverse Concatenated Path Down"; | |
816 | default: return "Invalid Diagnostic"; | |
817 | } | |
818 | }; | |
819 | ||
820 | static void | |
821 | log_msg(enum vlog_level level, const struct msg *p, const char *message, | |
bd3950dd | 822 | const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
823 | { |
824 | struct ds ds = DS_EMPTY_INITIALIZER; | |
825 | ||
826 | if (vlog_should_drop(THIS_MODULE, level, &rl)) { | |
827 | return; | |
828 | } | |
829 | ||
830 | ds_put_format(&ds, | |
831 | "%s: %s." | |
832 | "\n\tvers:%"PRIu8" diag:\"%s\" state:%s mult:%"PRIu8 | |
833 | " length:%"PRIu8 | |
834 | "\n\tflags: %s" | |
835 | "\n\tmy_disc:0x%"PRIx32" your_disc:0x%"PRIx32 | |
836 | "\n\tmin_tx:%"PRIu32"us (%"PRIu32"ms)" | |
837 | "\n\tmin_rx:%"PRIu32"us (%"PRIu32"ms)" | |
838 | "\n\tmin_rx_echo:%"PRIu32"us (%"PRIu32"ms)", | |
839 | bfd->name, message, p->vers_diag >> VERS_SHIFT, | |
840 | bfd_diag_str(p->vers_diag & DIAG_MASK), | |
841 | bfd_state_str(p->flags & STATE_MASK), | |
842 | p->mult, p->length, bfd_flag_str(p->flags & FLAGS_MASK), | |
843 | ntohl(p->my_disc), ntohl(p->your_disc), | |
844 | ntohl(p->min_tx), ntohl(p->min_tx) / 1000, | |
845 | ntohl(p->min_rx), ntohl(p->min_rx) / 1000, | |
846 | ntohl(p->min_rx_echo), ntohl(p->min_rx_echo) / 1000); | |
847 | bfd_put_details(&ds, bfd); | |
848 | VLOG(level, "%s", ds_cstr(&ds)); | |
849 | ds_destroy(&ds); | |
850 | } | |
851 | ||
852 | static void | |
853 | bfd_set_state(struct bfd *bfd, enum state state, enum diag diag) | |
bd3950dd | 854 | OVS_REQUIRES(mutex) |
ccc09689 EJ |
855 | { |
856 | if (diag == DIAG_NONE && bfd->cpath_down) { | |
857 | diag = DIAG_CPATH_DOWN; | |
858 | } | |
859 | ||
860 | if (bfd->state != state || bfd->diag != diag) { | |
861 | if (!VLOG_DROP_INFO(&rl)) { | |
862 | struct ds ds = DS_EMPTY_INITIALIZER; | |
863 | ||
864 | ds_put_format(&ds, "%s: BFD state change: %s->%s" | |
865 | " \"%s\"->\"%s\".\n", | |
866 | bfd->name, bfd_state_str(bfd->state), | |
867 | bfd_state_str(state), bfd_diag_str(bfd->diag), | |
868 | bfd_diag_str(diag)); | |
869 | bfd_put_details(&ds, bfd); | |
870 | VLOG_INFO("%s", ds_cstr(&ds)); | |
871 | ds_destroy(&ds); | |
872 | } | |
873 | ||
874 | bfd->state = state; | |
875 | bfd->diag = diag; | |
876 | ||
877 | if (bfd->state <= STATE_DOWN) { | |
878 | bfd->rmt_state = STATE_DOWN; | |
879 | bfd->rmt_diag = DIAG_NONE; | |
880 | bfd->rmt_min_rx = 1; | |
881 | bfd->rmt_flags = 0; | |
882 | bfd->rmt_disc = 0; | |
883 | bfd->rmt_min_tx = 0; | |
884 | } | |
885 | } | |
886 | } | |
887 | ||
888 | static uint32_t | |
889 | generate_discriminator(void) | |
890 | { | |
891 | uint32_t disc = 0; | |
892 | ||
893 | /* RFC 5880 Section 6.8.1 | |
894 | * It SHOULD be set to a random (but still unique) value to improve | |
895 | * security. The value is otherwise outside the scope of this | |
896 | * specification. */ | |
897 | ||
898 | while (!disc) { | |
899 | struct bfd *bfd; | |
900 | ||
5798ed6d | 901 | /* 'disc' is by definition random, so there's no reason to waste time |
ccc09689 EJ |
902 | * hashing it. */ |
903 | disc = random_uint32(); | |
26131299 | 904 | HMAP_FOR_EACH_IN_BUCKET (bfd, node, disc, all_bfds) { |
ccc09689 EJ |
905 | if (bfd->disc == disc) { |
906 | disc = 0; | |
907 | break; | |
908 | } | |
909 | } | |
910 | } | |
911 | ||
912 | return disc; | |
913 | } | |
914 | ||
915 | static struct bfd * | |
bd3950dd | 916 | bfd_find_by_name(const char *name) OVS_REQUIRES(mutex) |
ccc09689 EJ |
917 | { |
918 | struct bfd *bfd; | |
919 | ||
26131299 | 920 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
ccc09689 EJ |
921 | if (!strcmp(bfd->name, name)) { |
922 | return bfd; | |
923 | } | |
924 | } | |
925 | return NULL; | |
926 | } | |
927 | ||
928 | static void | |
bd3950dd | 929 | bfd_put_details(struct ds *ds, const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
930 | { |
931 | ds_put_format(ds, "\tForwarding: %s\n", | |
26131299 | 932 | bfd_forwarding__(bfd) ? "true" : "false"); |
ccc09689 EJ |
933 | ds_put_format(ds, "\tDetect Multiplier: %d\n", bfd->mult); |
934 | ds_put_format(ds, "\tConcatenated Path Down: %s\n", | |
935 | bfd->cpath_down ? "true" : "false"); | |
936 | ds_put_format(ds, "\tTX Interval: Approx %lldms\n", bfd_tx_interval(bfd)); | |
937 | ds_put_format(ds, "\tRX Interval: Approx %lldms\n", bfd_rx_interval(bfd)); | |
938 | ds_put_format(ds, "\tDetect Time: now %+lldms\n", | |
939 | time_msec() - bfd->detect_time); | |
940 | ds_put_format(ds, "\tNext TX Time: now %+lldms\n", | |
941 | time_msec() - bfd->next_tx); | |
942 | ds_put_format(ds, "\tLast TX Time: now %+lldms\n", | |
943 | time_msec() - bfd->last_tx); | |
944 | ||
945 | ds_put_cstr(ds, "\n"); | |
946 | ||
947 | ds_put_format(ds, "\tLocal Flags: %s\n", bfd_flag_str(bfd->flags)); | |
948 | ds_put_format(ds, "\tLocal Session State: %s\n", | |
949 | bfd_state_str(bfd->state)); | |
950 | ds_put_format(ds, "\tLocal Diagnostic: %s\n", bfd_diag_str(bfd->diag)); | |
951 | ds_put_format(ds, "\tLocal Discriminator: 0x%"PRIx32"\n", bfd->disc); | |
952 | ds_put_format(ds, "\tLocal Minimum TX Interval: %lldms\n", | |
953 | bfd_min_tx(bfd)); | |
954 | ds_put_format(ds, "\tLocal Minimum RX Interval: %lldms\n", bfd->min_rx); | |
955 | ||
956 | ds_put_cstr(ds, "\n"); | |
957 | ||
958 | ds_put_format(ds, "\tRemote Flags: %s\n", bfd_flag_str(bfd->rmt_flags)); | |
959 | ds_put_format(ds, "\tRemote Session State: %s\n", | |
960 | bfd_state_str(bfd->rmt_state)); | |
961 | ds_put_format(ds, "\tRemote Diagnostic: %s\n", | |
962 | bfd_diag_str(bfd->rmt_diag)); | |
963 | ds_put_format(ds, "\tRemote Discriminator: 0x%"PRIx32"\n", bfd->rmt_disc); | |
964 | ds_put_format(ds, "\tRemote Minimum TX Interval: %lldms\n", | |
965 | bfd->rmt_min_tx); | |
966 | ds_put_format(ds, "\tRemote Minimum RX Interval: %lldms\n", | |
967 | bfd->rmt_min_rx); | |
968 | } | |
969 | ||
970 | static void | |
971 | bfd_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], | |
26131299 | 972 | void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
973 | { |
974 | struct ds ds = DS_EMPTY_INITIALIZER; | |
975 | struct bfd *bfd; | |
976 | ||
26131299 | 977 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
978 | if (argc > 1) { |
979 | bfd = bfd_find_by_name(argv[1]); | |
980 | if (!bfd) { | |
981 | unixctl_command_reply_error(conn, "no such bfd object"); | |
26131299 | 982 | goto out; |
ccc09689 EJ |
983 | } |
984 | bfd_put_details(&ds, bfd); | |
985 | } else { | |
26131299 | 986 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
ccc09689 EJ |
987 | ds_put_format(&ds, "---- %s ----\n", bfd->name); |
988 | bfd_put_details(&ds, bfd); | |
989 | } | |
990 | } | |
991 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
992 | ds_destroy(&ds); | |
26131299 EJ |
993 | |
994 | out: | |
995 | ovs_mutex_unlock(&mutex); | |
ccc09689 | 996 | } |
91aaf124 PR |
997 | |
998 | ||
999 | static void | |
1000 | bfd_unixctl_set_forwarding_override(struct unixctl_conn *conn, int argc, | |
1001 | const char *argv[], void *aux OVS_UNUSED) | |
26131299 | 1002 | OVS_EXCLUDED(mutex) |
91aaf124 PR |
1003 | { |
1004 | const char *forward_str = argv[argc - 1]; | |
1005 | int forwarding_override; | |
1006 | struct bfd *bfd; | |
1007 | ||
26131299 | 1008 | ovs_mutex_lock(&mutex); |
91aaf124 PR |
1009 | if (!strcasecmp("true", forward_str)) { |
1010 | forwarding_override = 1; | |
1011 | } else if (!strcasecmp("false", forward_str)) { | |
1012 | forwarding_override = 0; | |
1013 | } else if (!strcasecmp("normal", forward_str)) { | |
1014 | forwarding_override = -1; | |
1015 | } else { | |
1016 | unixctl_command_reply_error(conn, "unknown fault string"); | |
26131299 | 1017 | goto out; |
91aaf124 PR |
1018 | } |
1019 | ||
1020 | if (argc > 2) { | |
1021 | bfd = bfd_find_by_name(argv[1]); | |
1022 | if (!bfd) { | |
1023 | unixctl_command_reply_error(conn, "no such BFD object"); | |
26131299 | 1024 | goto out; |
91aaf124 PR |
1025 | } |
1026 | bfd->forwarding_override = forwarding_override; | |
1027 | } else { | |
26131299 | 1028 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
91aaf124 PR |
1029 | bfd->forwarding_override = forwarding_override; |
1030 | } | |
1031 | } | |
1032 | ||
1033 | unixctl_command_reply(conn, "OK"); | |
26131299 EJ |
1034 | |
1035 | out: | |
1036 | ovs_mutex_unlock(&mutex); | |
91aaf124 | 1037 | } |