]>
Commit | Line | Data |
---|---|---|
8917f72c | 1 | /* Copyright (c) 2013, 2014 Nicira, Inc. |
ccc09689 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 | #include <config.h> | |
16 | #include "bfd.h" | |
17 | ||
b532f3f0 | 18 | #include <sys/types.h> |
ccc09689 | 19 | #include <arpa/inet.h> |
2fbf137d | 20 | #include <netinet/in_systm.h> |
b644259f | 21 | #include <netinet/ip.h> |
a48fd8eb | 22 | #include <sys/socket.h> |
ccc09689 | 23 | |
f645ee9c | 24 | #include "byte-order.h" |
f23d157c | 25 | #include "connectivity.h" |
ccc09689 EJ |
26 | #include "csum.h" |
27 | #include "dpif.h" | |
28 | #include "dynamic-string.h" | |
29 | #include "flow.h" | |
30 | #include "hash.h" | |
31 | #include "hmap.h" | |
32 | #include "list.h" | |
c1c4e8c7 | 33 | #include "netdev.h" |
ccc09689 EJ |
34 | #include "odp-util.h" |
35 | #include "ofpbuf.h" | |
26131299 | 36 | #include "ovs-thread.h" |
ccc09689 EJ |
37 | #include "openvswitch/types.h" |
38 | #include "packets.h" | |
39 | #include "poll-loop.h" | |
40 | #include "random.h" | |
f23d157c | 41 | #include "seq.h" |
ccc09689 EJ |
42 | #include "smap.h" |
43 | #include "timeval.h" | |
7c457c33 | 44 | #include "unaligned.h" |
ccc09689 EJ |
45 | #include "unixctl.h" |
46 | #include "util.h" | |
47 | #include "vlog.h" | |
48 | ||
49 | VLOG_DEFINE_THIS_MODULE(bfd); | |
50 | ||
51 | /* XXX Finish BFD. | |
52 | * | |
53 | * The goal of this module is to replace CFM with something both more flexible | |
54 | * and standards compliant. In service of this goal, the following needs to be | |
55 | * done. | |
56 | * | |
57 | * - Compliance | |
58 | * * Implement Demand mode. | |
59 | * * Go through the RFC line by line and verify we comply. | |
60 | * * Test against a hardware implementation. Preferably a popular one. | |
61 | * * Delete BFD packets with nw_ttl != 255 in the datapath to prevent DOS | |
62 | * attacks. | |
63 | * | |
64 | * - Unit tests. | |
65 | * | |
b644259f | 66 | * - Set TOS/PCP on the outer tunnel header when encapped. |
ccc09689 | 67 | * |
ccc09689 EJ |
68 | * - Sending BFD messages should be in its own thread/process. |
69 | * | |
70 | * - Scale testing. How does it operate when there are large number of bfd | |
71 | * sessions? Do we ever have random flaps? What's the CPU utilization? | |
72 | * | |
73 | * - Rely on data traffic for liveness by using BFD demand mode. | |
74 | * If we're receiving traffic on a port, we can safely assume it's up (modulo | |
75 | * unidrectional failures). BFD has a demand mode in which it can stay quiet | |
76 | * unless it feels the need to check the status of the port. Using this, we | |
77 | * can implement a strategy in which BFD only sends control messages on dark | |
78 | * interfaces. | |
79 | * | |
80 | * - Depending on how one interprets the spec, it appears that a BFD session | |
81 | * can never change bfd.LocalDiag to "No Diagnostic". We should verify that | |
82 | * this is what hardware implementations actually do. Seems like "No | |
83 | * Diagnostic" should be set once a BFD session state goes UP. */ | |
84 | ||
85 | #define BFD_VERSION 1 | |
86 | ||
87 | enum flags { | |
88 | FLAG_MULTIPOINT = 1 << 0, | |
89 | FLAG_DEMAND = 1 << 1, | |
90 | FLAG_AUTH = 1 << 2, | |
91 | FLAG_CTL = 1 << 3, | |
92 | FLAG_FINAL = 1 << 4, | |
93 | FLAG_POLL = 1 << 5 | |
94 | }; | |
95 | ||
96 | enum state { | |
97 | STATE_ADMIN_DOWN = 0 << 6, | |
98 | STATE_DOWN = 1 << 6, | |
99 | STATE_INIT = 2 << 6, | |
100 | STATE_UP = 3 << 6 | |
101 | }; | |
102 | ||
103 | enum diag { | |
104 | DIAG_NONE = 0, /* No Diagnostic. */ | |
105 | DIAG_EXPIRED = 1, /* Control Detection Time Expired. */ | |
106 | DIAG_ECHO_FAILED = 2, /* Echo Function Failed. */ | |
107 | DIAG_RMT_DOWN = 3, /* Neighbor Signaled Session Down. */ | |
108 | DIAG_FWD_RESET = 4, /* Forwarding Plane Reset. */ | |
109 | DIAG_PATH_DOWN = 5, /* Path Down. */ | |
110 | DIAG_CPATH_DOWN = 6, /* Concatenated Path Down. */ | |
111 | DIAG_ADMIN_DOWN = 7, /* Administratively Down. */ | |
112 | DIAG_RCPATH_DOWN = 8 /* Reverse Concatenated Path Down. */ | |
113 | }; | |
114 | ||
115 | /* RFC 5880 Section 4.1 | |
116 | * 0 1 2 3 | |
117 | * 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 | |
118 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
119 | * |Vers | Diag |Sta|P|F|C|A|D|M| Detect Mult | Length | | |
120 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
121 | * | My Discriminator | | |
122 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
123 | * | Your Discriminator | | |
124 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
125 | * | Desired Min TX Interval | | |
126 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
127 | * | Required Min RX Interval | | |
128 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ | |
129 | * | Required Min Echo RX Interval | | |
130 | * +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ */ | |
131 | struct msg { | |
132 | uint8_t vers_diag; /* Version and diagnostic. */ | |
133 | uint8_t flags; /* 2bit State field followed by flags. */ | |
134 | uint8_t mult; /* Fault detection multiplier. */ | |
135 | uint8_t length; /* Length of this BFD message. */ | |
136 | ovs_be32 my_disc; /* My discriminator. */ | |
137 | ovs_be32 your_disc; /* Your discriminator. */ | |
138 | ovs_be32 min_tx; /* Desired minimum tx interval. */ | |
139 | ovs_be32 min_rx; /* Required minimum rx interval. */ | |
140 | ovs_be32 min_rx_echo; /* Required minimum echo rx interval. */ | |
141 | }; | |
142 | BUILD_ASSERT_DECL(BFD_PACKET_LEN == sizeof(struct msg)); | |
143 | ||
144 | #define DIAG_MASK 0x1f | |
145 | #define VERS_SHIFT 5 | |
146 | #define STATE_MASK 0xC0 | |
147 | #define FLAGS_MASK 0x3f | |
148 | ||
149 | struct bfd { | |
150 | struct hmap_node node; /* In 'all_bfds'. */ | |
151 | uint32_t disc; /* bfd.LocalDiscr. Key in 'all_bfds' hmap. */ | |
152 | ||
153 | char *name; /* Name used for logging. */ | |
154 | ||
155 | bool cpath_down; /* Concatenated Path Down. */ | |
156 | uint8_t mult; /* bfd.DetectMult. */ | |
157 | ||
c1c4e8c7 AW |
158 | struct netdev *netdev; |
159 | uint64_t rx_packets; /* Packets received by 'netdev'. */ | |
160 | ||
ccc09689 EJ |
161 | enum state state; /* bfd.SessionState. */ |
162 | enum state rmt_state; /* bfd.RemoteSessionState. */ | |
163 | ||
164 | enum diag diag; /* bfd.LocalDiag. */ | |
165 | enum diag rmt_diag; /* Remote diagnostic. */ | |
166 | ||
167 | enum flags flags; /* Flags sent on messages. */ | |
168 | enum flags rmt_flags; /* Flags last received. */ | |
169 | ||
170 | uint32_t rmt_disc; /* bfd.RemoteDiscr. */ | |
171 | ||
873b049f AW |
172 | uint8_t local_eth_src[ETH_ADDR_LEN]; /* Local eth src address. */ |
173 | uint8_t local_eth_dst[ETH_ADDR_LEN]; /* Local eth dst address. */ | |
174 | ||
175 | uint8_t rmt_eth_dst[ETH_ADDR_LEN]; /* Remote eth dst address. */ | |
de8d2ef9 | 176 | |
dfe37e6a AW |
177 | ovs_be32 ip_src; /* IPv4 source address. */ |
178 | ovs_be32 ip_dst; /* IPv4 destination address. */ | |
179 | ||
ccc09689 EJ |
180 | uint16_t udp_src; /* UDP source port. */ |
181 | ||
182 | /* All timers in milliseconds. */ | |
183 | long long int rmt_min_rx; /* bfd.RemoteMinRxInterval. */ | |
184 | long long int rmt_min_tx; /* Remote minimum TX interval. */ | |
185 | ||
186 | long long int cfg_min_tx; /* Configured minimum TX rate. */ | |
187 | long long int cfg_min_rx; /* Configured required minimum RX rate. */ | |
188 | long long int poll_min_tx; /* Min TX negotating in a poll sequence. */ | |
189 | long long int poll_min_rx; /* Min RX negotating in a poll sequence. */ | |
190 | long long int min_tx; /* bfd.DesiredMinTxInterval. */ | |
191 | long long int min_rx; /* bfd.RequiredMinRxInterval. */ | |
192 | ||
193 | long long int last_tx; /* Last TX time. */ | |
194 | long long int next_tx; /* Next TX time. */ | |
195 | long long int detect_time; /* RFC 5880 6.8.4 Detection time. */ | |
92cfab82 | 196 | |
a1aeea86 | 197 | bool last_forwarding; /* Last calculation of forwarding flag. */ |
91aaf124 | 198 | int forwarding_override; /* Manual override of 'forwarding' status. */ |
26131299 EJ |
199 | |
200 | atomic_bool check_tnl_key; /* Verify tunnel key of inbound packets? */ | |
37bec3d3 | 201 | struct ovs_refcount ref_cnt; |
c1c4e8c7 | 202 | |
01d18a3a AW |
203 | /* When forward_if_rx is true, bfd_forwarding() will return |
204 | * true as long as there are incoming packets received. | |
205 | * Note, forwarding_override still has higher priority. */ | |
206 | bool forwarding_if_rx; | |
207 | long long int forwarding_if_rx_detect_time; | |
208 | ||
34c88624 AW |
209 | /* When 'bfd->forwarding_if_rx' is set, at least one bfd control packet |
210 | * is required to be received every 100 * bfd->cfg_min_rx. If bfd | |
211 | * control packet is not received within this interval, even if data | |
212 | * packets are received, the bfd->forwarding will still be false. */ | |
213 | long long int demand_rx_bfd_time; | |
214 | ||
c1c4e8c7 AW |
215 | /* BFD decay related variables. */ |
216 | bool in_decay; /* True when bfd is in decay. */ | |
217 | int decay_min_rx; /* min_rx is set to decay_min_rx when */ | |
218 | /* in decay. */ | |
219 | int decay_rx_ctl; /* Count bfd packets received within decay */ | |
220 | /* detect interval. */ | |
01d18a3a | 221 | uint64_t decay_rx_packets; /* Packets received by 'netdev'. */ |
c1c4e8c7 | 222 | long long int decay_detect_time; /* Decay detection time. */ |
4905e2df AW |
223 | |
224 | uint64_t flap_count; /* Counts bfd forwarding flaps. */ | |
88bf179a AW |
225 | |
226 | /* True when the variables returned by bfd_get_status() are changed | |
227 | * since last check. */ | |
228 | bool status_changed; | |
ccc09689 EJ |
229 | }; |
230 | ||
26131299 EJ |
231 | static struct ovs_mutex mutex = OVS_MUTEX_INITIALIZER; |
232 | static struct hmap all_bfds__ = HMAP_INITIALIZER(&all_bfds__); | |
233 | static struct hmap *const all_bfds OVS_GUARDED_BY(mutex) = &all_bfds__; | |
234 | ||
dfe37e6a AW |
235 | static bool bfd_lookup_ip(const char *host_name, struct in_addr *) |
236 | OVS_REQUIRES(mutex); | |
9cdc68a1 | 237 | static bool bfd_forwarding__(struct bfd *) OVS_REQUIRES(mutex); |
344e21d4 AW |
238 | static bool bfd_in_poll(const struct bfd *) OVS_REQUIRES(mutex); |
239 | static void bfd_poll(struct bfd *bfd) OVS_REQUIRES(mutex); | |
240 | static const char *bfd_diag_str(enum diag) OVS_REQUIRES(mutex); | |
241 | static const char *bfd_state_str(enum state) OVS_REQUIRES(mutex); | |
242 | static long long int bfd_min_tx(const struct bfd *) OVS_REQUIRES(mutex); | |
26131299 | 243 | static long long int bfd_tx_interval(const struct bfd *) |
344e21d4 | 244 | OVS_REQUIRES(mutex); |
26131299 | 245 | static long long int bfd_rx_interval(const struct bfd *) |
344e21d4 AW |
246 | OVS_REQUIRES(mutex); |
247 | static void bfd_set_next_tx(struct bfd *) OVS_REQUIRES(mutex); | |
26131299 | 248 | static void bfd_set_state(struct bfd *, enum state, enum diag) |
344e21d4 AW |
249 | OVS_REQUIRES(mutex); |
250 | static uint32_t generate_discriminator(void) OVS_REQUIRES(mutex); | |
26131299 | 251 | static void bfd_put_details(struct ds *, const struct bfd *) |
344e21d4 | 252 | OVS_REQUIRES(mutex); |
c1c4e8c7 AW |
253 | static uint64_t bfd_rx_packets(const struct bfd *) OVS_REQUIRES(mutex); |
254 | static void bfd_try_decay(struct bfd *) OVS_REQUIRES(mutex); | |
255 | static void bfd_decay_update(struct bfd *) OVS_REQUIRES(mutex); | |
88bf179a | 256 | static void bfd_status_changed(struct bfd *) OVS_REQUIRES(mutex); |
a1aeea86 | 257 | |
01d18a3a | 258 | static void bfd_forwarding_if_rx_update(struct bfd *) OVS_REQUIRES(mutex); |
ccc09689 EJ |
259 | static void bfd_unixctl_show(struct unixctl_conn *, int argc, |
260 | const char *argv[], void *aux OVS_UNUSED); | |
91aaf124 PR |
261 | static void bfd_unixctl_set_forwarding_override(struct unixctl_conn *, |
262 | int argc, const char *argv[], | |
263 | void *aux OVS_UNUSED); | |
ccc09689 | 264 | static void log_msg(enum vlog_level, const struct msg *, const char *message, |
344e21d4 | 265 | const struct bfd *) OVS_REQUIRES(mutex); |
ccc09689 EJ |
266 | |
267 | static struct vlog_rate_limit rl = VLOG_RATE_LIMIT_INIT(20, 20); | |
ccc09689 EJ |
268 | |
269 | /* Returns true if the interface on which 'bfd' is running may be used to | |
270 | * forward traffic according to the BFD session state. */ | |
271 | bool | |
9cdc68a1 | 272 | bfd_forwarding(struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 273 | { |
26131299 | 274 | bool ret; |
91aaf124 | 275 | |
26131299 EJ |
276 | ovs_mutex_lock(&mutex); |
277 | ret = bfd_forwarding__(bfd); | |
278 | ovs_mutex_unlock(&mutex); | |
279 | return ret; | |
ccc09689 EJ |
280 | } |
281 | ||
a1aeea86 AW |
282 | /* When forwarding_if_rx is enabled, if there are packets received, |
283 | * updates forwarding_if_rx_detect_time. */ | |
284 | void | |
285 | bfd_account_rx(struct bfd *bfd, const struct dpif_flow_stats *stats) | |
286 | { | |
287 | if (stats->n_packets && bfd->forwarding_if_rx) { | |
288 | ovs_mutex_lock(&mutex); | |
289 | bfd_forwarding__(bfd); | |
290 | bfd_forwarding_if_rx_update(bfd); | |
291 | bfd_forwarding__(bfd); | |
292 | ovs_mutex_unlock(&mutex); | |
293 | } | |
294 | } | |
295 | ||
88bf179a AW |
296 | /* Returns and resets the 'bfd->status_changed'. */ |
297 | bool | |
298 | bfd_check_status_change(struct bfd *bfd) OVS_EXCLUDED(mutex) | |
299 | { | |
300 | bool ret; | |
301 | ||
302 | ovs_mutex_lock(&mutex); | |
303 | ret = bfd->status_changed; | |
304 | bfd->status_changed = false; | |
305 | ovs_mutex_unlock(&mutex); | |
306 | ||
307 | return ret; | |
308 | } | |
309 | ||
ccc09689 EJ |
310 | /* Returns a 'smap' of key value pairs representing the status of 'bfd' |
311 | * intended for the OVS database. */ | |
312 | void | |
313 | bfd_get_status(const struct bfd *bfd, struct smap *smap) | |
26131299 | 314 | OVS_EXCLUDED(mutex) |
ccc09689 | 315 | { |
26131299 | 316 | ovs_mutex_lock(&mutex); |
a1aeea86 AW |
317 | smap_add(smap, "forwarding", |
318 | bfd_forwarding__(CONST_CAST(struct bfd *, bfd)) | |
319 | ? "true" : "false"); | |
ccc09689 EJ |
320 | smap_add(smap, "state", bfd_state_str(bfd->state)); |
321 | smap_add(smap, "diagnostic", bfd_diag_str(bfd->diag)); | |
4905e2df | 322 | smap_add_format(smap, "flap_count", "%"PRIu64, bfd->flap_count); |
ccc09689 EJ |
323 | |
324 | if (bfd->state != STATE_DOWN) { | |
325 | smap_add(smap, "remote_state", bfd_state_str(bfd->rmt_state)); | |
326 | smap_add(smap, "remote_diagnostic", bfd_diag_str(bfd->rmt_diag)); | |
327 | } | |
26131299 | 328 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
329 | } |
330 | ||
331 | /* Initializes, destroys, or reconfigures the BFD session 'bfd' (named 'name'), | |
332 | * according to the database configuration contained in 'cfg'. Takes ownership | |
333 | * of 'bfd', which may be NULL. Returns a BFD object which may be used as a | |
8aee94b6 PR |
334 | * handle for the session, or NULL if BFD is not enabled according to 'cfg'. |
335 | * Also returns NULL if cfg is NULL. */ | |
ccc09689 | 336 | struct bfd * |
c1c4e8c7 AW |
337 | bfd_configure(struct bfd *bfd, const char *name, const struct smap *cfg, |
338 | struct netdev *netdev) OVS_EXCLUDED(mutex) | |
ccc09689 | 339 | { |
26131299 | 340 | static struct ovsthread_once once = OVSTHREAD_ONCE_INITIALIZER; |
94f756da | 341 | static atomic_count udp_src = ATOMIC_COUNT_INIT(0); |
ccc09689 | 342 | |
c1c4e8c7 | 343 | int decay_min_rx; |
ccc09689 | 344 | long long int min_tx, min_rx; |
2a833280 | 345 | bool need_poll = false; |
c1c4e8c7 | 346 | bool cfg_min_rx_changed = false; |
01d18a3a | 347 | bool cpath_down, forwarding_if_rx; |
dfe37e6a AW |
348 | const char *hwaddr, *ip_src, *ip_dst; |
349 | struct in_addr in_addr; | |
de8d2ef9 | 350 | uint8_t ea[ETH_ADDR_LEN]; |
ccc09689 | 351 | |
26131299 | 352 | if (ovsthread_once_start(&once)) { |
ccc09689 EJ |
353 | unixctl_command_register("bfd/show", "[interface]", 0, 1, |
354 | bfd_unixctl_show, NULL); | |
91aaf124 PR |
355 | unixctl_command_register("bfd/set-forwarding", |
356 | "[interface] normal|false|true", 1, 2, | |
357 | bfd_unixctl_set_forwarding_override, NULL); | |
26131299 | 358 | ovsthread_once_done(&once); |
ccc09689 EJ |
359 | } |
360 | ||
8aee94b6 | 361 | if (!cfg || !smap_get_bool(cfg, "enable", false)) { |
92cfab82 | 362 | bfd_unref(bfd); |
ccc09689 EJ |
363 | return NULL; |
364 | } | |
365 | ||
26131299 | 366 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
367 | if (!bfd) { |
368 | bfd = xzalloc(sizeof *bfd); | |
369 | bfd->name = xstrdup(name); | |
91aaf124 | 370 | bfd->forwarding_override = -1; |
ccc09689 | 371 | bfd->disc = generate_discriminator(); |
26131299 | 372 | hmap_insert(all_bfds, &bfd->node, bfd->disc); |
ccc09689 EJ |
373 | |
374 | bfd->diag = DIAG_NONE; | |
375 | bfd->min_tx = 1000; | |
376 | bfd->mult = 3; | |
37bec3d3 | 377 | ovs_refcount_init(&bfd->ref_cnt); |
c1c4e8c7 | 378 | bfd->netdev = netdev_ref(netdev); |
01d18a3a | 379 | bfd->rx_packets = bfd_rx_packets(bfd); |
c1c4e8c7 | 380 | bfd->in_decay = false; |
4905e2df | 381 | bfd->flap_count = 0; |
ccc09689 EJ |
382 | |
383 | /* RFC 5881 section 4 | |
384 | * The source port MUST be in the range 49152 through 65535. The same | |
385 | * UDP source port number MUST be used for all BFD Control packets | |
386 | * associated with a particular session. The source port number SHOULD | |
387 | * be unique among all BFD sessions on the system. */ | |
94f756da | 388 | bfd->udp_src = (atomic_count_inc(&udp_src) % 16384) + 49152; |
ccc09689 EJ |
389 | |
390 | bfd_set_state(bfd, STATE_DOWN, DIAG_NONE); | |
de8d2ef9 | 391 | |
5383f2c2 | 392 | bfd_status_changed(bfd); |
ccc09689 EJ |
393 | } |
394 | ||
94f756da JR |
395 | atomic_store_relaxed(&bfd->check_tnl_key, |
396 | smap_get_bool(cfg, "check_tnl_key", false)); | |
ccc09689 | 397 | min_tx = smap_get_int(cfg, "min_tx", 100); |
3f5ce9ef | 398 | min_tx = MAX(min_tx, 1); |
ccc09689 EJ |
399 | if (bfd->cfg_min_tx != min_tx) { |
400 | bfd->cfg_min_tx = min_tx; | |
401 | if (bfd->state != STATE_UP | |
402 | || (!bfd_in_poll(bfd) && bfd->cfg_min_tx < bfd->min_tx)) { | |
403 | bfd->min_tx = bfd->cfg_min_tx; | |
404 | } | |
2a833280 | 405 | need_poll = true; |
ccc09689 EJ |
406 | } |
407 | ||
408 | min_rx = smap_get_int(cfg, "min_rx", 1000); | |
3f5ce9ef | 409 | min_rx = MAX(min_rx, 1); |
ccc09689 EJ |
410 | if (bfd->cfg_min_rx != min_rx) { |
411 | bfd->cfg_min_rx = min_rx; | |
412 | if (bfd->state != STATE_UP | |
413 | || (!bfd_in_poll(bfd) && bfd->cfg_min_rx > bfd->min_rx)) { | |
414 | bfd->min_rx = bfd->cfg_min_rx; | |
415 | } | |
c1c4e8c7 AW |
416 | cfg_min_rx_changed = true; |
417 | need_poll = true; | |
418 | } | |
419 | ||
420 | decay_min_rx = smap_get_int(cfg, "decay_min_rx", 0); | |
421 | if (bfd->decay_min_rx != decay_min_rx || cfg_min_rx_changed) { | |
422 | if (decay_min_rx > 0 && decay_min_rx < bfd->cfg_min_rx) { | |
423 | VLOG_WARN("%s: decay_min_rx cannot be less than %lld ms", | |
424 | bfd->name, bfd->cfg_min_rx); | |
425 | bfd->decay_min_rx = 0; | |
426 | } else { | |
427 | bfd->decay_min_rx = decay_min_rx; | |
428 | } | |
429 | /* Resets decay. */ | |
430 | bfd->in_decay = false; | |
431 | bfd_decay_update(bfd); | |
2a833280 | 432 | need_poll = true; |
ccc09689 EJ |
433 | } |
434 | ||
435 | cpath_down = smap_get_bool(cfg, "cpath_down", false); | |
436 | if (bfd->cpath_down != cpath_down) { | |
437 | bfd->cpath_down = cpath_down; | |
615309cf | 438 | bfd_set_state(bfd, bfd->state, DIAG_NONE); |
2a833280 | 439 | need_poll = true; |
ccc09689 | 440 | } |
de8d2ef9 | 441 | |
873b049f AW |
442 | hwaddr = smap_get(cfg, "bfd_local_src_mac"); |
443 | if (hwaddr && eth_addr_from_string(hwaddr, ea)) { | |
444 | memcpy(bfd->local_eth_src, ea, ETH_ADDR_LEN); | |
445 | } else { | |
446 | memset(bfd->local_eth_src, 0, ETH_ADDR_LEN); | |
447 | } | |
448 | ||
449 | hwaddr = smap_get(cfg, "bfd_local_dst_mac"); | |
450 | if (hwaddr && eth_addr_from_string(hwaddr, ea)) { | |
451 | memcpy(bfd->local_eth_dst, ea, ETH_ADDR_LEN); | |
452 | } else { | |
453 | memset(bfd->local_eth_dst, 0, ETH_ADDR_LEN); | |
454 | } | |
455 | ||
456 | hwaddr = smap_get(cfg, "bfd_remote_dst_mac"); | |
457 | if (hwaddr && eth_addr_from_string(hwaddr, ea)) { | |
458 | memcpy(bfd->rmt_eth_dst, ea, ETH_ADDR_LEN); | |
459 | } else { | |
460 | memset(bfd->rmt_eth_dst, 0, ETH_ADDR_LEN); | |
de8d2ef9 GS |
461 | } |
462 | ||
dfe37e6a AW |
463 | ip_src = smap_get(cfg, "bfd_src_ip"); |
464 | if (ip_src && bfd_lookup_ip(ip_src, &in_addr)) { | |
465 | memcpy(&bfd->ip_src, &in_addr, sizeof in_addr); | |
466 | } else { | |
1314739c | 467 | bfd->ip_src = htonl(0xA9FE0101); /* 169.254.1.1. */ |
dfe37e6a AW |
468 | } |
469 | ||
470 | ip_dst = smap_get(cfg, "bfd_dst_ip"); | |
471 | if (ip_dst && bfd_lookup_ip(ip_dst, &in_addr)) { | |
472 | memcpy(&bfd->ip_dst, &in_addr, sizeof in_addr); | |
473 | } else { | |
1314739c | 474 | bfd->ip_dst = htonl(0xA9FE0100); /* 169.254.1.0. */ |
dfe37e6a AW |
475 | } |
476 | ||
01d18a3a AW |
477 | forwarding_if_rx = smap_get_bool(cfg, "forwarding_if_rx", false); |
478 | if (bfd->forwarding_if_rx != forwarding_if_rx) { | |
479 | bfd->forwarding_if_rx = forwarding_if_rx; | |
480 | if (bfd->state == STATE_UP && bfd->forwarding_if_rx) { | |
481 | bfd_forwarding_if_rx_update(bfd); | |
482 | } else { | |
483 | bfd->forwarding_if_rx_detect_time = 0; | |
484 | } | |
485 | } | |
486 | ||
2a833280 AW |
487 | if (need_poll) { |
488 | bfd_poll(bfd); | |
489 | } | |
26131299 | 490 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
491 | return bfd; |
492 | } | |
493 | ||
92cfab82 EJ |
494 | struct bfd * |
495 | bfd_ref(const struct bfd *bfd_) | |
496 | { | |
497 | struct bfd *bfd = CONST_CAST(struct bfd *, bfd_); | |
498 | if (bfd) { | |
37bec3d3 | 499 | ovs_refcount_ref(&bfd->ref_cnt); |
92cfab82 EJ |
500 | } |
501 | return bfd; | |
502 | } | |
503 | ||
504 | void | |
26131299 | 505 | bfd_unref(struct bfd *bfd) OVS_EXCLUDED(mutex) |
92cfab82 | 506 | { |
24f83812 | 507 | if (bfd && ovs_refcount_unref_relaxed(&bfd->ref_cnt) == 1) { |
37bec3d3 | 508 | ovs_mutex_lock(&mutex); |
5383f2c2 | 509 | bfd_status_changed(bfd); |
37bec3d3 BP |
510 | hmap_remove(all_bfds, &bfd->node); |
511 | netdev_close(bfd->netdev); | |
37bec3d3 BP |
512 | free(bfd->name); |
513 | free(bfd); | |
514 | ovs_mutex_unlock(&mutex); | |
92cfab82 EJ |
515 | } |
516 | } | |
517 | ||
ccc09689 | 518 | void |
26131299 | 519 | bfd_wait(const struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 520 | { |
88e4462e AW |
521 | poll_timer_wait_until(bfd_wake_time(bfd)); |
522 | } | |
523 | ||
524 | /* Returns the next wake up time. */ | |
525 | long long int | |
526 | bfd_wake_time(const struct bfd *bfd) OVS_EXCLUDED(mutex) | |
527 | { | |
528 | long long int retval; | |
529 | ||
530 | if (!bfd) { | |
531 | return LLONG_MAX; | |
ccc09689 EJ |
532 | } |
533 | ||
88e4462e AW |
534 | ovs_mutex_lock(&mutex); |
535 | if (bfd->flags & FLAG_FINAL) { | |
536 | retval = 0; | |
537 | } else { | |
538 | retval = bfd->next_tx; | |
539 | if (bfd->state > STATE_DOWN) { | |
540 | retval = MIN(bfd->detect_time, retval); | |
541 | } | |
ccc09689 | 542 | } |
26131299 | 543 | ovs_mutex_unlock(&mutex); |
88e4462e | 544 | return retval; |
ccc09689 EJ |
545 | } |
546 | ||
547 | void | |
26131299 | 548 | bfd_run(struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 549 | { |
c1c4e8c7 AW |
550 | long long int now; |
551 | bool old_in_decay; | |
552 | ||
26131299 | 553 | ovs_mutex_lock(&mutex); |
c1c4e8c7 AW |
554 | now = time_msec(); |
555 | old_in_decay = bfd->in_decay; | |
556 | ||
557 | if (bfd->state > STATE_DOWN && now >= bfd->detect_time) { | |
ccc09689 EJ |
558 | bfd_set_state(bfd, STATE_DOWN, DIAG_EXPIRED); |
559 | } | |
f23d157c | 560 | bfd_forwarding__(bfd); |
ccc09689 | 561 | |
c1c4e8c7 AW |
562 | /* Decay may only happen when state is STATE_UP, bfd->decay_min_rx is |
563 | * configured, and decay_detect_time is reached. */ | |
564 | if (bfd->state == STATE_UP && bfd->decay_min_rx > 0 | |
565 | && now >= bfd->decay_detect_time) { | |
566 | bfd_try_decay(bfd); | |
567 | } | |
568 | ||
569 | if (bfd->min_tx != bfd->cfg_min_tx | |
570 | || (bfd->min_rx != bfd->cfg_min_rx && bfd->min_rx != bfd->decay_min_rx) | |
571 | || bfd->in_decay != old_in_decay) { | |
ccc09689 EJ |
572 | bfd_poll(bfd); |
573 | } | |
26131299 | 574 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
575 | } |
576 | ||
577 | bool | |
26131299 | 578 | bfd_should_send_packet(const struct bfd *bfd) OVS_EXCLUDED(mutex) |
ccc09689 | 579 | { |
26131299 EJ |
580 | bool ret; |
581 | ovs_mutex_lock(&mutex); | |
582 | ret = bfd->flags & FLAG_FINAL || time_msec() >= bfd->next_tx; | |
583 | ovs_mutex_unlock(&mutex); | |
584 | return ret; | |
ccc09689 EJ |
585 | } |
586 | ||
587 | void | |
588 | bfd_put_packet(struct bfd *bfd, struct ofpbuf *p, | |
26131299 | 589 | uint8_t eth_src[ETH_ADDR_LEN]) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
590 | { |
591 | long long int min_tx, min_rx; | |
592 | struct udp_header *udp; | |
593 | struct eth_header *eth; | |
594 | struct ip_header *ip; | |
595 | struct msg *msg; | |
596 | ||
26131299 | 597 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
598 | if (bfd->next_tx) { |
599 | long long int delay = time_msec() - bfd->next_tx; | |
600 | long long int interval = bfd_tx_interval(bfd); | |
601 | if (delay > interval * 3 / 2) { | |
6a690106 | 602 | VLOG_INFO("%s: long delay of %lldms (expected %lldms) sending BFD" |
ccc09689 EJ |
603 | " control message", bfd->name, delay, interval); |
604 | } | |
605 | } | |
606 | ||
607 | /* RFC 5880 Section 6.5 | |
608 | * A BFD Control packet MUST NOT have both the Poll (P) and Final (F) bits | |
609 | * set. */ | |
610 | ovs_assert(!(bfd->flags & FLAG_POLL) || !(bfd->flags & FLAG_FINAL)); | |
611 | ||
612 | ofpbuf_reserve(p, 2); /* Properly align after the ethernet header. */ | |
613 | eth = ofpbuf_put_uninit(p, sizeof *eth); | |
873b049f AW |
614 | memcpy(eth->eth_src, |
615 | eth_addr_is_zero(bfd->local_eth_src) ? eth_src | |
616 | : bfd->local_eth_src, | |
617 | ETH_ADDR_LEN); | |
618 | memcpy(eth->eth_dst, | |
619 | eth_addr_is_zero(bfd->local_eth_dst) ? eth_addr_bfd | |
620 | : bfd->local_eth_dst, | |
621 | ETH_ADDR_LEN); | |
ccc09689 EJ |
622 | eth->eth_type = htons(ETH_TYPE_IP); |
623 | ||
624 | ip = ofpbuf_put_zeros(p, sizeof *ip); | |
625 | ip->ip_ihl_ver = IP_IHL_VER(5, 4); | |
626 | ip->ip_tot_len = htons(sizeof *ip + sizeof *udp + sizeof *msg); | |
b644259f PR |
627 | ip->ip_ttl = MAXTTL; |
628 | ip->ip_tos = IPTOS_LOWDELAY | IPTOS_THROUGHPUT; | |
ccc09689 | 629 | ip->ip_proto = IPPROTO_UDP; |
dfe37e6a AW |
630 | put_16aligned_be32(&ip->ip_src, bfd->ip_src); |
631 | put_16aligned_be32(&ip->ip_dst, bfd->ip_dst); | |
ccc09689 EJ |
632 | ip->ip_csum = csum(ip, sizeof *ip); |
633 | ||
634 | udp = ofpbuf_put_zeros(p, sizeof *udp); | |
635 | udp->udp_src = htons(bfd->udp_src); | |
636 | udp->udp_dst = htons(BFD_DEST_PORT); | |
637 | udp->udp_len = htons(sizeof *udp + sizeof *msg); | |
638 | ||
639 | msg = ofpbuf_put_uninit(p, sizeof *msg); | |
640 | msg->vers_diag = (BFD_VERSION << 5) | bfd->diag; | |
641 | msg->flags = (bfd->state & STATE_MASK) | bfd->flags; | |
642 | ||
643 | msg->mult = bfd->mult; | |
644 | msg->length = BFD_PACKET_LEN; | |
645 | msg->my_disc = htonl(bfd->disc); | |
646 | msg->your_disc = htonl(bfd->rmt_disc); | |
647 | msg->min_rx_echo = htonl(0); | |
648 | ||
649 | if (bfd_in_poll(bfd)) { | |
650 | min_tx = bfd->poll_min_tx; | |
651 | min_rx = bfd->poll_min_rx; | |
652 | } else { | |
653 | min_tx = bfd_min_tx(bfd); | |
654 | min_rx = bfd->min_rx; | |
655 | } | |
656 | ||
657 | msg->min_tx = htonl(min_tx * 1000); | |
658 | msg->min_rx = htonl(min_rx * 1000); | |
659 | ||
660 | bfd->flags &= ~FLAG_FINAL; | |
661 | ||
662 | log_msg(VLL_DBG, msg, "Sending BFD Message", bfd); | |
663 | ||
664 | bfd->last_tx = time_msec(); | |
665 | bfd_set_next_tx(bfd); | |
26131299 | 666 | ovs_mutex_unlock(&mutex); |
ccc09689 EJ |
667 | } |
668 | ||
669 | bool | |
5675cb4c | 670 | bfd_should_process_flow(const struct bfd *bfd_, const struct flow *flow, |
fab52e16 | 671 | struct flow_wildcards *wc) |
ccc09689 | 672 | { |
5675cb4c | 673 | struct bfd *bfd = CONST_CAST(struct bfd *, bfd_); |
5675cb4c | 674 | |
94f756da JR |
675 | if (!eth_addr_is_zero(bfd->rmt_eth_dst)) { |
676 | memset(&wc->masks.dl_dst, 0xff, sizeof wc->masks.dl_dst); | |
26131299 | 677 | |
94f756da JR |
678 | if (memcmp(bfd->rmt_eth_dst, flow->dl_dst, ETH_ADDR_LEN)) { |
679 | return false; | |
680 | } | |
681 | } | |
26131299 | 682 | |
94f756da JR |
683 | if (flow->dl_type == htons(ETH_TYPE_IP)) { |
684 | memset(&wc->masks.nw_proto, 0xff, sizeof wc->masks.nw_proto); | |
685 | if (flow->nw_proto == IPPROTO_UDP) { | |
686 | memset(&wc->masks.tp_dst, 0xff, sizeof wc->masks.tp_dst); | |
687 | if (flow->tp_dst == htons(BFD_DEST_PORT)) { | |
688 | bool check_tnl_key; | |
689 | ||
690 | atomic_read_relaxed(&bfd->check_tnl_key, &check_tnl_key); | |
691 | if (check_tnl_key) { | |
692 | memset(&wc->masks.tunnel.tun_id, 0xff, | |
693 | sizeof wc->masks.tunnel.tun_id); | |
694 | return flow->tunnel.tun_id == htonll(0); | |
695 | } | |
696 | return true; | |
697 | } | |
698 | } | |
fab52e16 | 699 | } |
94f756da | 700 | return false; |
ccc09689 EJ |
701 | } |
702 | ||
703 | void | |
704 | bfd_process_packet(struct bfd *bfd, const struct flow *flow, | |
26131299 | 705 | const struct ofpbuf *p) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
706 | { |
707 | uint32_t rmt_min_rx, pkt_your_disc; | |
708 | enum state rmt_state; | |
709 | enum flags flags; | |
710 | uint8_t version; | |
711 | struct msg *msg; | |
5a51b2cd JR |
712 | const uint8_t *l7 = ofpbuf_get_udp_payload(p); |
713 | ||
714 | if (!l7) { | |
715 | return; /* No UDP payload. */ | |
716 | } | |
ccc09689 EJ |
717 | |
718 | /* This function is designed to follow section RFC 5880 6.8.6 closely. */ | |
719 | ||
26131299 | 720 | ovs_mutex_lock(&mutex); |
c1c4e8c7 AW |
721 | /* Increments the decay rx counter. */ |
722 | bfd->decay_rx_ctl++; | |
723 | ||
a1aeea86 AW |
724 | bfd_forwarding__(bfd); |
725 | ||
ccc09689 EJ |
726 | if (flow->nw_ttl != 255) { |
727 | /* XXX Should drop in the kernel to prevent DOS. */ | |
26131299 | 728 | goto out; |
ccc09689 EJ |
729 | } |
730 | ||
1f317cb5 | 731 | msg = ofpbuf_at(p, l7 - (uint8_t *)ofpbuf_data(p), BFD_PACKET_LEN); |
ccc09689 | 732 | if (!msg) { |
bc47dcfd | 733 | VLOG_INFO_RL(&rl, "%s: Received too-short BFD control message (only " |
34582733 | 734 | "%"PRIdPTR" bytes long, at least %d required).", |
5a51b2cd | 735 | bfd->name, (uint8_t *) ofpbuf_tail(p) - l7, |
bc47dcfd | 736 | BFD_PACKET_LEN); |
26131299 | 737 | goto out; |
ccc09689 EJ |
738 | } |
739 | ||
740 | /* RFC 5880 Section 6.8.6 | |
741 | * If the Length field is greater than the payload of the encapsulating | |
742 | * protocol, the packet MUST be discarded. | |
743 | * | |
744 | * Note that we make this check implicity. Above we use ofpbuf_at() to | |
745 | * ensure that there are at least BFD_PACKET_LEN bytes in the payload of | |
746 | * the encapsulating protocol. Below we require msg->length to be exactly | |
747 | * BFD_PACKET_LEN bytes. */ | |
748 | ||
749 | flags = msg->flags & FLAGS_MASK; | |
750 | rmt_state = msg->flags & STATE_MASK; | |
751 | version = msg->vers_diag >> VERS_SHIFT; | |
752 | ||
753 | log_msg(VLL_DBG, msg, "Received BFD control message", bfd); | |
754 | ||
755 | if (version != BFD_VERSION) { | |
756 | log_msg(VLL_WARN, msg, "Incorrect version", bfd); | |
26131299 | 757 | goto out; |
ccc09689 EJ |
758 | } |
759 | ||
760 | /* Technically this should happen after the length check. We don't support | |
761 | * authentication however, so it's simpler to do the check first. */ | |
762 | if (flags & FLAG_AUTH) { | |
763 | log_msg(VLL_WARN, msg, "Authenticated control message with" | |
764 | " authentication disabled", bfd); | |
26131299 | 765 | goto out; |
ccc09689 EJ |
766 | } |
767 | ||
768 | if (msg->length != BFD_PACKET_LEN) { | |
769 | log_msg(VLL_WARN, msg, "Unexpected length", bfd); | |
770 | if (msg->length < BFD_PACKET_LEN) { | |
26131299 | 771 | goto out; |
ccc09689 EJ |
772 | } |
773 | } | |
774 | ||
775 | if (!msg->mult) { | |
776 | log_msg(VLL_WARN, msg, "Zero multiplier", bfd); | |
26131299 | 777 | goto out; |
ccc09689 EJ |
778 | } |
779 | ||
780 | if (flags & FLAG_MULTIPOINT) { | |
781 | log_msg(VLL_WARN, msg, "Unsupported multipoint flag", bfd); | |
26131299 | 782 | goto out; |
ccc09689 EJ |
783 | } |
784 | ||
785 | if (!msg->my_disc) { | |
786 | log_msg(VLL_WARN, msg, "NULL my_disc", bfd); | |
26131299 | 787 | goto out; |
ccc09689 EJ |
788 | } |
789 | ||
790 | pkt_your_disc = ntohl(msg->your_disc); | |
791 | if (pkt_your_disc) { | |
792 | /* Technically, we should use the your discriminator field to figure | |
793 | * out which 'struct bfd' this packet is destined towards. That way a | |
794 | * bfd session could migrate from one interface to another | |
795 | * transparently. This doesn't fit in with the OVS structure very | |
796 | * well, so in this respect, we are not compliant. */ | |
797 | if (pkt_your_disc != bfd->disc) { | |
798 | log_msg(VLL_WARN, msg, "Incorrect your_disc", bfd); | |
26131299 | 799 | goto out; |
ccc09689 EJ |
800 | } |
801 | } else if (rmt_state > STATE_DOWN) { | |
802 | log_msg(VLL_WARN, msg, "Null your_disc", bfd); | |
26131299 | 803 | goto out; |
ccc09689 EJ |
804 | } |
805 | ||
9c6c453e | 806 | if (bfd->rmt_state != rmt_state) { |
88bf179a | 807 | bfd_status_changed(bfd); |
9c6c453e JS |
808 | } |
809 | ||
ccc09689 EJ |
810 | bfd->rmt_disc = ntohl(msg->my_disc); |
811 | bfd->rmt_state = rmt_state; | |
812 | bfd->rmt_flags = flags; | |
813 | bfd->rmt_diag = msg->vers_diag & DIAG_MASK; | |
814 | ||
815 | if (flags & FLAG_FINAL && bfd_in_poll(bfd)) { | |
816 | bfd->min_tx = bfd->poll_min_tx; | |
817 | bfd->min_rx = bfd->poll_min_rx; | |
818 | bfd->flags &= ~FLAG_POLL; | |
819 | log_msg(VLL_INFO, msg, "Poll sequence terminated", bfd); | |
820 | } | |
821 | ||
822 | if (flags & FLAG_POLL) { | |
823 | /* RFC 5880 Section 6.5 | |
824 | * When the other system receives a Poll, it immediately transmits a | |
825 | * BFD Control packet with the Final (F) bit set, independent of any | |
826 | * periodic BFD Control packets it may be sending | |
827 | * (see section 6.8.7). */ | |
828 | bfd->flags &= ~FLAG_POLL; | |
829 | bfd->flags |= FLAG_FINAL; | |
830 | } | |
831 | ||
832 | rmt_min_rx = MAX(ntohl(msg->min_rx) / 1000, 1); | |
833 | if (bfd->rmt_min_rx != rmt_min_rx) { | |
834 | bfd->rmt_min_rx = rmt_min_rx; | |
122bbcc4 JS |
835 | if (bfd->next_tx) { |
836 | bfd_set_next_tx(bfd); | |
837 | } | |
ccc09689 EJ |
838 | log_msg(VLL_INFO, msg, "New remote min_rx", bfd); |
839 | } | |
840 | ||
841 | bfd->rmt_min_tx = MAX(ntohl(msg->min_tx) / 1000, 1); | |
842 | bfd->detect_time = bfd_rx_interval(bfd) * bfd->mult + time_msec(); | |
843 | ||
844 | if (bfd->state == STATE_ADMIN_DOWN) { | |
845 | VLOG_DBG_RL(&rl, "Administratively down, dropping control message."); | |
26131299 | 846 | goto out; |
ccc09689 EJ |
847 | } |
848 | ||
849 | if (rmt_state == STATE_ADMIN_DOWN) { | |
850 | if (bfd->state != STATE_DOWN) { | |
851 | bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN); | |
852 | } | |
853 | } else { | |
854 | switch (bfd->state) { | |
855 | case STATE_DOWN: | |
856 | if (rmt_state == STATE_DOWN) { | |
857 | bfd_set_state(bfd, STATE_INIT, bfd->diag); | |
858 | } else if (rmt_state == STATE_INIT) { | |
859 | bfd_set_state(bfd, STATE_UP, bfd->diag); | |
860 | } | |
861 | break; | |
862 | case STATE_INIT: | |
863 | if (rmt_state > STATE_DOWN) { | |
864 | bfd_set_state(bfd, STATE_UP, bfd->diag); | |
865 | } | |
866 | break; | |
867 | case STATE_UP: | |
868 | if (rmt_state <= STATE_DOWN) { | |
869 | bfd_set_state(bfd, STATE_DOWN, DIAG_RMT_DOWN); | |
870 | log_msg(VLL_INFO, msg, "Remote signaled STATE_DOWN", bfd); | |
871 | } | |
872 | break; | |
873 | case STATE_ADMIN_DOWN: | |
874 | default: | |
428b2edd | 875 | OVS_NOT_REACHED(); |
ccc09689 EJ |
876 | } |
877 | } | |
878 | /* XXX: RFC 5880 Section 6.8.6 Demand mode related calculations here. */ | |
26131299 | 879 | |
34c88624 AW |
880 | if (bfd->forwarding_if_rx) { |
881 | bfd->demand_rx_bfd_time = time_msec() + 100 * bfd->cfg_min_rx; | |
882 | } | |
883 | ||
26131299 | 884 | out: |
a1aeea86 | 885 | bfd_forwarding__(bfd); |
26131299 | 886 | ovs_mutex_unlock(&mutex); |
ccc09689 | 887 | } |
c1c4e8c7 AW |
888 | |
889 | /* Must be called when the netdev owned by 'bfd' should change. */ | |
890 | void | |
891 | bfd_set_netdev(struct bfd *bfd, const struct netdev *netdev) | |
892 | OVS_EXCLUDED(mutex) | |
893 | { | |
894 | ovs_mutex_lock(&mutex); | |
895 | if (bfd->netdev != netdev) { | |
896 | netdev_close(bfd->netdev); | |
897 | bfd->netdev = netdev_ref(netdev); | |
01d18a3a | 898 | if (bfd->decay_min_rx && bfd->state == STATE_UP) { |
c1c4e8c7 AW |
899 | bfd_decay_update(bfd); |
900 | } | |
01d18a3a AW |
901 | if (bfd->forwarding_if_rx && bfd->state == STATE_UP) { |
902 | bfd_forwarding_if_rx_update(bfd); | |
903 | } | |
904 | bfd->rx_packets = bfd_rx_packets(bfd); | |
c1c4e8c7 AW |
905 | } |
906 | ovs_mutex_unlock(&mutex); | |
907 | } | |
908 | ||
ccc09689 | 909 | \f |
4905e2df | 910 | /* Updates the forwarding flag. If override is not configured and |
a1aeea86 AW |
911 | * the forwarding flag value changes, increments the flap count. |
912 | * | |
913 | * Note this function may be called multiple times in a function | |
914 | * (e.g. bfd_account_rx) before and after the bfd state or status | |
915 | * change. This is to capture any forwarding flag flap. */ | |
26131299 | 916 | static bool |
9cdc68a1 | 917 | bfd_forwarding__(struct bfd *bfd) OVS_REQUIRES(mutex) |
26131299 | 918 | { |
34c88624 AW |
919 | long long int now = time_msec(); |
920 | bool forwarding_if_rx; | |
a1aeea86 | 921 | bool last_forwarding = bfd->last_forwarding; |
01d18a3a | 922 | |
26131299 EJ |
923 | if (bfd->forwarding_override != -1) { |
924 | return bfd->forwarding_override == 1; | |
925 | } | |
926 | ||
34c88624 AW |
927 | forwarding_if_rx = bfd->forwarding_if_rx |
928 | && bfd->forwarding_if_rx_detect_time > now | |
929 | && bfd->demand_rx_bfd_time > now; | |
930 | ||
931 | bfd->last_forwarding = (bfd->state == STATE_UP || forwarding_if_rx) | |
932 | && bfd->rmt_diag != DIAG_PATH_DOWN | |
933 | && bfd->rmt_diag != DIAG_CPATH_DOWN | |
934 | && bfd->rmt_diag != DIAG_RCPATH_DOWN; | |
a1aeea86 | 935 | if (bfd->last_forwarding != last_forwarding) { |
4905e2df | 936 | bfd->flap_count++; |
88bf179a | 937 | bfd_status_changed(bfd); |
4905e2df | 938 | } |
a1aeea86 | 939 | return bfd->last_forwarding; |
26131299 EJ |
940 | } |
941 | ||
ccc09689 | 942 | /* Helpers. */ |
dfe37e6a AW |
943 | static bool |
944 | bfd_lookup_ip(const char *host_name, struct in_addr *addr) | |
945 | { | |
1bbd1728 | 946 | if (!inet_pton(AF_INET, host_name, addr)) { |
dfe37e6a AW |
947 | VLOG_ERR_RL(&rl, "\"%s\" is not a valid IP address", host_name); |
948 | return false; | |
949 | } | |
950 | return true; | |
951 | } | |
952 | ||
ccc09689 | 953 | static bool |
bd3950dd | 954 | bfd_in_poll(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
955 | { |
956 | return (bfd->flags & FLAG_POLL) != 0; | |
957 | } | |
958 | ||
959 | static void | |
bd3950dd | 960 | bfd_poll(struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
961 | { |
962 | if (bfd->state > STATE_DOWN && !bfd_in_poll(bfd) | |
963 | && !(bfd->flags & FLAG_FINAL)) { | |
964 | bfd->poll_min_tx = bfd->cfg_min_tx; | |
c1c4e8c7 | 965 | bfd->poll_min_rx = bfd->in_decay ? bfd->decay_min_rx : bfd->cfg_min_rx; |
ccc09689 EJ |
966 | bfd->flags |= FLAG_POLL; |
967 | bfd->next_tx = 0; | |
968 | VLOG_INFO_RL(&rl, "%s: Initiating poll sequence", bfd->name); | |
969 | } | |
970 | } | |
971 | ||
972 | static long long int | |
bd3950dd | 973 | bfd_min_tx(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
974 | { |
975 | /* RFC 5880 Section 6.8.3 | |
976 | * When bfd.SessionState is not Up, the system MUST set | |
977 | * bfd.DesiredMinTxInterval to a value of not less than one second | |
978 | * (1,000,000 microseconds). This is intended to ensure that the | |
979 | * bandwidth consumed by BFD sessions that are not Up is negligible, | |
980 | * particularly in the case where a neighbor may not be running BFD. */ | |
981 | return (bfd->state == STATE_UP ? bfd->min_tx : MAX(bfd->min_tx, 1000)); | |
982 | } | |
983 | ||
984 | static long long int | |
bd3950dd | 985 | bfd_tx_interval(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
986 | { |
987 | long long int interval = bfd_min_tx(bfd); | |
988 | return MAX(interval, bfd->rmt_min_rx); | |
989 | } | |
990 | ||
991 | static long long int | |
bd3950dd | 992 | bfd_rx_interval(const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
993 | { |
994 | return MAX(bfd->min_rx, bfd->rmt_min_tx); | |
995 | } | |
996 | ||
997 | static void | |
bd3950dd | 998 | bfd_set_next_tx(struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
999 | { |
1000 | long long int interval = bfd_tx_interval(bfd); | |
1001 | interval -= interval * random_range(26) / 100; | |
1002 | bfd->next_tx = bfd->last_tx + interval; | |
1003 | } | |
1004 | ||
1005 | static const char * | |
1006 | bfd_flag_str(enum flags flags) | |
1007 | { | |
1008 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1009 | static char flag_str[128]; | |
1010 | ||
1011 | if (!flags) { | |
1012 | return "none"; | |
1013 | } | |
1014 | ||
1015 | if (flags & FLAG_MULTIPOINT) { | |
1016 | ds_put_cstr(&ds, "multipoint "); | |
1017 | } | |
1018 | ||
1019 | if (flags & FLAG_DEMAND) { | |
1020 | ds_put_cstr(&ds, "demand "); | |
1021 | } | |
1022 | ||
1023 | if (flags & FLAG_AUTH) { | |
1024 | ds_put_cstr(&ds, "auth "); | |
1025 | } | |
1026 | ||
1027 | if (flags & FLAG_CTL) { | |
1028 | ds_put_cstr(&ds, "ctl "); | |
1029 | } | |
1030 | ||
1031 | if (flags & FLAG_FINAL) { | |
1032 | ds_put_cstr(&ds, "final "); | |
1033 | } | |
1034 | ||
1035 | if (flags & FLAG_POLL) { | |
1036 | ds_put_cstr(&ds, "poll "); | |
1037 | } | |
1038 | ||
70e575d9 AW |
1039 | /* Do not copy the trailing whitespace. */ |
1040 | ds_chomp(&ds, ' '); | |
ccc09689 EJ |
1041 | ovs_strlcpy(flag_str, ds_cstr(&ds), sizeof flag_str); |
1042 | ds_destroy(&ds); | |
1043 | return flag_str; | |
1044 | } | |
1045 | ||
1046 | static const char * | |
1047 | bfd_state_str(enum state state) | |
1048 | { | |
1049 | switch (state) { | |
1050 | case STATE_ADMIN_DOWN: return "admin_down"; | |
1051 | case STATE_DOWN: return "down"; | |
1052 | case STATE_INIT: return "init"; | |
1053 | case STATE_UP: return "up"; | |
1054 | default: return "invalid"; | |
1055 | } | |
1056 | } | |
1057 | ||
1058 | static const char * | |
1059 | bfd_diag_str(enum diag diag) { | |
1060 | switch (diag) { | |
1061 | case DIAG_NONE: return "No Diagnostic"; | |
1062 | case DIAG_EXPIRED: return "Control Detection Time Expired"; | |
1063 | case DIAG_ECHO_FAILED: return "Echo Function Failed"; | |
1064 | case DIAG_RMT_DOWN: return "Neighbor Signaled Session Down"; | |
1065 | case DIAG_FWD_RESET: return "Forwarding Plane Reset"; | |
1066 | case DIAG_PATH_DOWN: return "Path Down"; | |
1067 | case DIAG_CPATH_DOWN: return "Concatenated Path Down"; | |
1068 | case DIAG_ADMIN_DOWN: return "Administratively Down"; | |
1069 | case DIAG_RCPATH_DOWN: return "Reverse Concatenated Path Down"; | |
1070 | default: return "Invalid Diagnostic"; | |
1071 | } | |
1072 | }; | |
1073 | ||
1074 | static void | |
1075 | log_msg(enum vlog_level level, const struct msg *p, const char *message, | |
bd3950dd | 1076 | const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 EJ |
1077 | { |
1078 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1079 | ||
1080 | if (vlog_should_drop(THIS_MODULE, level, &rl)) { | |
1081 | return; | |
1082 | } | |
1083 | ||
1084 | ds_put_format(&ds, | |
1085 | "%s: %s." | |
1086 | "\n\tvers:%"PRIu8" diag:\"%s\" state:%s mult:%"PRIu8 | |
1087 | " length:%"PRIu8 | |
1088 | "\n\tflags: %s" | |
1089 | "\n\tmy_disc:0x%"PRIx32" your_disc:0x%"PRIx32 | |
1090 | "\n\tmin_tx:%"PRIu32"us (%"PRIu32"ms)" | |
1091 | "\n\tmin_rx:%"PRIu32"us (%"PRIu32"ms)" | |
1092 | "\n\tmin_rx_echo:%"PRIu32"us (%"PRIu32"ms)", | |
1093 | bfd->name, message, p->vers_diag >> VERS_SHIFT, | |
1094 | bfd_diag_str(p->vers_diag & DIAG_MASK), | |
1095 | bfd_state_str(p->flags & STATE_MASK), | |
1096 | p->mult, p->length, bfd_flag_str(p->flags & FLAGS_MASK), | |
1097 | ntohl(p->my_disc), ntohl(p->your_disc), | |
1098 | ntohl(p->min_tx), ntohl(p->min_tx) / 1000, | |
1099 | ntohl(p->min_rx), ntohl(p->min_rx) / 1000, | |
1100 | ntohl(p->min_rx_echo), ntohl(p->min_rx_echo) / 1000); | |
1101 | bfd_put_details(&ds, bfd); | |
1102 | VLOG(level, "%s", ds_cstr(&ds)); | |
1103 | ds_destroy(&ds); | |
1104 | } | |
1105 | ||
1106 | static void | |
1107 | bfd_set_state(struct bfd *bfd, enum state state, enum diag diag) | |
bd3950dd | 1108 | OVS_REQUIRES(mutex) |
ccc09689 | 1109 | { |
615309cf | 1110 | if (bfd->cpath_down) { |
ccc09689 EJ |
1111 | diag = DIAG_CPATH_DOWN; |
1112 | } | |
1113 | ||
1114 | if (bfd->state != state || bfd->diag != diag) { | |
1115 | if (!VLOG_DROP_INFO(&rl)) { | |
1116 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1117 | ||
1118 | ds_put_format(&ds, "%s: BFD state change: %s->%s" | |
1119 | " \"%s\"->\"%s\".\n", | |
1120 | bfd->name, bfd_state_str(bfd->state), | |
1121 | bfd_state_str(state), bfd_diag_str(bfd->diag), | |
1122 | bfd_diag_str(diag)); | |
1123 | bfd_put_details(&ds, bfd); | |
1124 | VLOG_INFO("%s", ds_cstr(&ds)); | |
1125 | ds_destroy(&ds); | |
1126 | } | |
1127 | ||
1128 | bfd->state = state; | |
1129 | bfd->diag = diag; | |
1130 | ||
1131 | if (bfd->state <= STATE_DOWN) { | |
1132 | bfd->rmt_state = STATE_DOWN; | |
1133 | bfd->rmt_diag = DIAG_NONE; | |
1134 | bfd->rmt_min_rx = 1; | |
1135 | bfd->rmt_flags = 0; | |
1136 | bfd->rmt_disc = 0; | |
1137 | bfd->rmt_min_tx = 0; | |
c1c4e8c7 AW |
1138 | /* Resets the min_rx if in_decay. */ |
1139 | if (bfd->in_decay) { | |
1140 | bfd->min_rx = bfd->cfg_min_rx; | |
1141 | bfd->in_decay = false; | |
1142 | } | |
ccc09689 | 1143 | } |
c1c4e8c7 AW |
1144 | /* Resets the decay when state changes to STATE_UP |
1145 | * and decay_min_rx is configured. */ | |
1146 | if (bfd->state == STATE_UP && bfd->decay_min_rx) { | |
1147 | bfd_decay_update(bfd); | |
1148 | } | |
f23d157c | 1149 | |
88bf179a | 1150 | bfd_status_changed(bfd); |
c1c4e8c7 AW |
1151 | } |
1152 | } | |
1153 | ||
1154 | static uint64_t | |
1155 | bfd_rx_packets(const struct bfd *bfd) OVS_REQUIRES(mutex) | |
1156 | { | |
1157 | struct netdev_stats stats; | |
1158 | ||
1159 | if (!netdev_get_stats(bfd->netdev, &stats)) { | |
1160 | return stats.rx_packets; | |
1161 | } else { | |
1162 | return 0; | |
ccc09689 EJ |
1163 | } |
1164 | } | |
1165 | ||
c1c4e8c7 AW |
1166 | /* Decays the bfd->min_rx to bfd->decay_min_rx when 'diff' is less than |
1167 | * the 'expect' value. */ | |
1168 | static void | |
1169 | bfd_try_decay(struct bfd *bfd) OVS_REQUIRES(mutex) | |
1170 | { | |
1171 | int64_t diff, expect; | |
1172 | ||
1173 | /* The 'diff' is the difference between current interface rx_packets | |
1174 | * stats and last-time check. The 'expect' is the recorded number of | |
1175 | * bfd control packets received within an approximately decay_min_rx | |
1176 | * (2000 ms if decay_min_rx is less than 2000 ms) interval. | |
1177 | * | |
1178 | * Since the update of rx_packets stats at interface happens | |
1179 | * asynchronously to the bfd_rx_packets() function, the 'diff' value | |
1180 | * can be jittered. Thusly, we double the decay_rx_ctl to provide | |
1181 | * more wiggle room. */ | |
01d18a3a | 1182 | diff = bfd_rx_packets(bfd) - bfd->decay_rx_packets; |
c1c4e8c7 AW |
1183 | expect = 2 * MAX(bfd->decay_rx_ctl, 1); |
1184 | bfd->in_decay = diff <= expect ? true : false; | |
1185 | bfd_decay_update(bfd); | |
1186 | } | |
1187 | ||
1188 | /* Updates the rx_packets, decay_rx_ctl and decay_detect_time. */ | |
1189 | static void | |
1190 | bfd_decay_update(struct bfd * bfd) OVS_REQUIRES(mutex) | |
1191 | { | |
01d18a3a | 1192 | bfd->decay_rx_packets = bfd_rx_packets(bfd); |
c1c4e8c7 AW |
1193 | bfd->decay_rx_ctl = 0; |
1194 | bfd->decay_detect_time = MAX(bfd->decay_min_rx, 2000) + time_msec(); | |
1195 | } | |
1196 | ||
88bf179a AW |
1197 | /* Records the status change and changes the global connectivity seq. */ |
1198 | static void | |
1199 | bfd_status_changed(struct bfd *bfd) OVS_REQUIRES(mutex) | |
1200 | { | |
1201 | seq_change(connectivity_seq_get()); | |
1202 | bfd->status_changed = true; | |
1203 | } | |
1204 | ||
01d18a3a AW |
1205 | static void |
1206 | bfd_forwarding_if_rx_update(struct bfd *bfd) OVS_REQUIRES(mutex) | |
1207 | { | |
1208 | int64_t incr = bfd_rx_interval(bfd) * bfd->mult; | |
1209 | bfd->forwarding_if_rx_detect_time = MAX(incr, 2000) + time_msec(); | |
1210 | } | |
1211 | ||
ccc09689 EJ |
1212 | static uint32_t |
1213 | generate_discriminator(void) | |
1214 | { | |
1215 | uint32_t disc = 0; | |
1216 | ||
1217 | /* RFC 5880 Section 6.8.1 | |
1218 | * It SHOULD be set to a random (but still unique) value to improve | |
1219 | * security. The value is otherwise outside the scope of this | |
1220 | * specification. */ | |
1221 | ||
1222 | while (!disc) { | |
1223 | struct bfd *bfd; | |
1224 | ||
5798ed6d | 1225 | /* 'disc' is by definition random, so there's no reason to waste time |
ccc09689 EJ |
1226 | * hashing it. */ |
1227 | disc = random_uint32(); | |
26131299 | 1228 | HMAP_FOR_EACH_IN_BUCKET (bfd, node, disc, all_bfds) { |
ccc09689 EJ |
1229 | if (bfd->disc == disc) { |
1230 | disc = 0; | |
1231 | break; | |
1232 | } | |
1233 | } | |
1234 | } | |
1235 | ||
1236 | return disc; | |
1237 | } | |
1238 | ||
1239 | static struct bfd * | |
bd3950dd | 1240 | bfd_find_by_name(const char *name) OVS_REQUIRES(mutex) |
ccc09689 EJ |
1241 | { |
1242 | struct bfd *bfd; | |
1243 | ||
26131299 | 1244 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
ccc09689 EJ |
1245 | if (!strcmp(bfd->name, name)) { |
1246 | return bfd; | |
1247 | } | |
1248 | } | |
1249 | return NULL; | |
1250 | } | |
1251 | ||
1252 | static void | |
bd3950dd | 1253 | bfd_put_details(struct ds *ds, const struct bfd *bfd) OVS_REQUIRES(mutex) |
ccc09689 | 1254 | { |
a1aeea86 AW |
1255 | ds_put_format(ds, "\tForwarding: %s\n", |
1256 | bfd_forwarding__(CONST_CAST(struct bfd *, bfd)) | |
1257 | ? "true" : "false"); | |
ccc09689 EJ |
1258 | ds_put_format(ds, "\tDetect Multiplier: %d\n", bfd->mult); |
1259 | ds_put_format(ds, "\tConcatenated Path Down: %s\n", | |
1260 | bfd->cpath_down ? "true" : "false"); | |
1261 | ds_put_format(ds, "\tTX Interval: Approx %lldms\n", bfd_tx_interval(bfd)); | |
1262 | ds_put_format(ds, "\tRX Interval: Approx %lldms\n", bfd_rx_interval(bfd)); | |
1263 | ds_put_format(ds, "\tDetect Time: now %+lldms\n", | |
1264 | time_msec() - bfd->detect_time); | |
1265 | ds_put_format(ds, "\tNext TX Time: now %+lldms\n", | |
1266 | time_msec() - bfd->next_tx); | |
1267 | ds_put_format(ds, "\tLast TX Time: now %+lldms\n", | |
1268 | time_msec() - bfd->last_tx); | |
1269 | ||
1270 | ds_put_cstr(ds, "\n"); | |
1271 | ||
1272 | ds_put_format(ds, "\tLocal Flags: %s\n", bfd_flag_str(bfd->flags)); | |
1273 | ds_put_format(ds, "\tLocal Session State: %s\n", | |
1274 | bfd_state_str(bfd->state)); | |
1275 | ds_put_format(ds, "\tLocal Diagnostic: %s\n", bfd_diag_str(bfd->diag)); | |
1276 | ds_put_format(ds, "\tLocal Discriminator: 0x%"PRIx32"\n", bfd->disc); | |
1277 | ds_put_format(ds, "\tLocal Minimum TX Interval: %lldms\n", | |
1278 | bfd_min_tx(bfd)); | |
1279 | ds_put_format(ds, "\tLocal Minimum RX Interval: %lldms\n", bfd->min_rx); | |
1280 | ||
1281 | ds_put_cstr(ds, "\n"); | |
1282 | ||
1283 | ds_put_format(ds, "\tRemote Flags: %s\n", bfd_flag_str(bfd->rmt_flags)); | |
1284 | ds_put_format(ds, "\tRemote Session State: %s\n", | |
1285 | bfd_state_str(bfd->rmt_state)); | |
1286 | ds_put_format(ds, "\tRemote Diagnostic: %s\n", | |
1287 | bfd_diag_str(bfd->rmt_diag)); | |
1288 | ds_put_format(ds, "\tRemote Discriminator: 0x%"PRIx32"\n", bfd->rmt_disc); | |
1289 | ds_put_format(ds, "\tRemote Minimum TX Interval: %lldms\n", | |
1290 | bfd->rmt_min_tx); | |
1291 | ds_put_format(ds, "\tRemote Minimum RX Interval: %lldms\n", | |
1292 | bfd->rmt_min_rx); | |
1293 | } | |
1294 | ||
1295 | static void | |
1296 | bfd_unixctl_show(struct unixctl_conn *conn, int argc, const char *argv[], | |
26131299 | 1297 | void *aux OVS_UNUSED) OVS_EXCLUDED(mutex) |
ccc09689 EJ |
1298 | { |
1299 | struct ds ds = DS_EMPTY_INITIALIZER; | |
1300 | struct bfd *bfd; | |
1301 | ||
26131299 | 1302 | ovs_mutex_lock(&mutex); |
ccc09689 EJ |
1303 | if (argc > 1) { |
1304 | bfd = bfd_find_by_name(argv[1]); | |
1305 | if (!bfd) { | |
1306 | unixctl_command_reply_error(conn, "no such bfd object"); | |
26131299 | 1307 | goto out; |
ccc09689 EJ |
1308 | } |
1309 | bfd_put_details(&ds, bfd); | |
1310 | } else { | |
26131299 | 1311 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
ccc09689 EJ |
1312 | ds_put_format(&ds, "---- %s ----\n", bfd->name); |
1313 | bfd_put_details(&ds, bfd); | |
1314 | } | |
1315 | } | |
1316 | unixctl_command_reply(conn, ds_cstr(&ds)); | |
1317 | ds_destroy(&ds); | |
26131299 EJ |
1318 | |
1319 | out: | |
1320 | ovs_mutex_unlock(&mutex); | |
ccc09689 | 1321 | } |
91aaf124 PR |
1322 | |
1323 | ||
1324 | static void | |
1325 | bfd_unixctl_set_forwarding_override(struct unixctl_conn *conn, int argc, | |
1326 | const char *argv[], void *aux OVS_UNUSED) | |
26131299 | 1327 | OVS_EXCLUDED(mutex) |
91aaf124 PR |
1328 | { |
1329 | const char *forward_str = argv[argc - 1]; | |
1330 | int forwarding_override; | |
1331 | struct bfd *bfd; | |
1332 | ||
26131299 | 1333 | ovs_mutex_lock(&mutex); |
91aaf124 PR |
1334 | if (!strcasecmp("true", forward_str)) { |
1335 | forwarding_override = 1; | |
1336 | } else if (!strcasecmp("false", forward_str)) { | |
1337 | forwarding_override = 0; | |
1338 | } else if (!strcasecmp("normal", forward_str)) { | |
1339 | forwarding_override = -1; | |
1340 | } else { | |
1341 | unixctl_command_reply_error(conn, "unknown fault string"); | |
26131299 | 1342 | goto out; |
91aaf124 PR |
1343 | } |
1344 | ||
1345 | if (argc > 2) { | |
1346 | bfd = bfd_find_by_name(argv[1]); | |
1347 | if (!bfd) { | |
1348 | unixctl_command_reply_error(conn, "no such BFD object"); | |
26131299 | 1349 | goto out; |
91aaf124 PR |
1350 | } |
1351 | bfd->forwarding_override = forwarding_override; | |
88bf179a | 1352 | bfd_status_changed(bfd); |
91aaf124 | 1353 | } else { |
26131299 | 1354 | HMAP_FOR_EACH (bfd, node, all_bfds) { |
91aaf124 | 1355 | bfd->forwarding_override = forwarding_override; |
88bf179a | 1356 | bfd_status_changed(bfd); |
91aaf124 PR |
1357 | } |
1358 | } | |
1359 | ||
1360 | unixctl_command_reply(conn, "OK"); | |
26131299 EJ |
1361 | |
1362 | out: | |
1363 | ovs_mutex_unlock(&mutex); | |
91aaf124 | 1364 | } |