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