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