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