]>
Commit | Line | Data |
---|---|---|
52df8228 CF |
1 | /* |
2 | * IS-IS Rout(e)ing protocol - BFD support | |
52df8228 CF |
3 | * Copyright (C) 2018 Christian Franke |
4 | * | |
d04f9fbf CF |
5 | * This program is free software; you can redistribute it and/or modify it |
6 | * under the terms of the GNU General Public License as published by the Free | |
7 | * Software Foundation; either version 2 of the License, or (at your option) | |
8 | * any later version. | |
52df8228 | 9 | * |
d04f9fbf CF |
10 | * This program is distributed in the hope that it will be useful, but WITHOUT |
11 | * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or | |
12 | * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for | |
13 | * more details. | |
52df8228 CF |
14 | * |
15 | * You should have received a copy of the GNU General Public License along | |
16 | * with this program; see the file COPYING; if not, write to the Free Software | |
17 | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA | |
18 | */ | |
19 | #include <zebra.h> | |
20 | ||
21 | #include "zclient.h" | |
e782cca7 | 22 | #include "nexthop.h" |
52df8228 | 23 | #include "bfd.h" |
e782cca7 | 24 | #include "lib_errors.h" |
52df8228 CF |
25 | |
26 | #include "isisd/isis_bfd.h" | |
27 | #include "isisd/isis_zebra.h" | |
215eccb0 CF |
28 | #include "isisd/isis_common.h" |
29 | #include "isisd/isis_constants.h" | |
30 | #include "isisd/isis_adjacency.h" | |
31 | #include "isisd/isis_circuit.h" | |
3015e3d1 | 32 | #include "isisd/isisd.h" |
215eccb0 | 33 | #include "isisd/fabricd.h" |
52df8228 | 34 | |
20a42f01 CF |
35 | DEFINE_MTYPE_STATIC(ISISD, BFD_SESSION, "ISIS BFD Session") |
36 | ||
37 | struct bfd_session { | |
e782cca7 RW |
38 | int family; |
39 | union g_addr dst_ip; | |
40 | union g_addr src_ip; | |
a5eba4e9 | 41 | int status; |
20a42f01 CF |
42 | }; |
43 | ||
e782cca7 RW |
44 | static struct bfd_session *bfd_session_new(int family, union g_addr *dst_ip, |
45 | union g_addr *src_ip) | |
20a42f01 CF |
46 | { |
47 | struct bfd_session *rv; | |
48 | ||
a5eba4e9 | 49 | rv = XCALLOC(MTYPE_BFD_SESSION, sizeof(*rv)); |
e782cca7 | 50 | rv->family = family; |
20a42f01 CF |
51 | rv->dst_ip = *dst_ip; |
52 | rv->src_ip = *src_ip; | |
53 | return rv; | |
54 | } | |
55 | ||
56 | static void bfd_session_free(struct bfd_session **session) | |
57 | { | |
58 | if (!*session) | |
59 | return; | |
60 | ||
61 | XFREE(MTYPE_BFD_SESSION, *session); | |
62 | *session = NULL; | |
63 | } | |
64 | ||
e782cca7 RW |
65 | static bool bfd_session_same(const struct bfd_session *session, int family, |
66 | const union g_addr *src, const union g_addr *dst) | |
67 | { | |
68 | if (session->family != family) | |
69 | return false; | |
70 | ||
71 | switch (session->family) { | |
72 | case AF_INET: | |
73 | if (!IPV4_ADDR_SAME(&session->dst_ip.ipv4, &dst->ipv4)) | |
74 | return false; | |
75 | if (!IPV4_ADDR_SAME(&session->src_ip.ipv4, &src->ipv4)) | |
76 | return false; | |
77 | break; | |
78 | case AF_INET6: | |
79 | if (!IPV6_ADDR_SAME(&session->dst_ip.ipv6, &dst->ipv6)) | |
80 | return false; | |
81 | if (!IPV6_ADDR_SAME(&session->src_ip.ipv6, &src->ipv6)) | |
82 | return false; | |
83 | break; | |
84 | default: | |
85 | flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", | |
86 | __func__, session->family); | |
87 | exit(1); | |
88 | } | |
89 | ||
90 | return true; | |
91 | } | |
92 | ||
a5eba4e9 CF |
93 | static void bfd_adj_event(struct isis_adjacency *adj, struct prefix *dst, |
94 | int new_status) | |
95 | { | |
96 | if (!adj->bfd_session) | |
97 | return; | |
98 | ||
e782cca7 | 99 | if (adj->bfd_session->family != dst->family) |
a5eba4e9 CF |
100 | return; |
101 | ||
e782cca7 RW |
102 | switch (adj->bfd_session->family) { |
103 | case AF_INET: | |
104 | if (!IPV4_ADDR_SAME(&adj->bfd_session->dst_ip.ipv4, | |
105 | &dst->u.prefix4)) | |
106 | return; | |
107 | break; | |
108 | case AF_INET6: | |
109 | if (!IPV6_ADDR_SAME(&adj->bfd_session->dst_ip.ipv6, | |
110 | &dst->u.prefix6)) | |
111 | return; | |
112 | break; | |
113 | default: | |
114 | flog_err(EC_LIB_DEVELOPMENT, "%s: unknown address-family: %u", | |
115 | __func__, adj->bfd_session->family); | |
116 | exit(1); | |
117 | } | |
118 | ||
a5eba4e9 | 119 | int old_status = adj->bfd_session->status; |
a5eba4e9 | 120 | |
7555dc61 S |
121 | BFD_SET_CLIENT_STATUS(adj->bfd_session->status, new_status); |
122 | ||
a5eba4e9 CF |
123 | if (old_status == new_status) |
124 | return; | |
125 | ||
126 | if (isis->debugs & DEBUG_BFD) { | |
127 | char dst_str[INET6_ADDRSTRLEN]; | |
128 | ||
e782cca7 | 129 | inet_ntop(adj->bfd_session->family, &adj->bfd_session->dst_ip, |
a5eba4e9 CF |
130 | dst_str, sizeof(dst_str)); |
131 | zlog_debug("ISIS-BFD: Peer %s on %s changed from %s to %s", | |
132 | dst_str, adj->circuit->interface->name, | |
133 | bfd_get_status_str(old_status), | |
134 | bfd_get_status_str(new_status)); | |
135 | } | |
136 | ||
137 | if (old_status != BFD_STATUS_UP | |
138 | || new_status != BFD_STATUS_DOWN) { | |
139 | return; | |
140 | } | |
141 | ||
142 | isis_adj_state_change(adj, ISIS_ADJ_DOWN, "bfd session went down"); | |
143 | } | |
144 | ||
121f9dee | 145 | static int isis_bfd_interface_dest_update(ZAPI_CALLBACK_ARGS) |
52df8228 | 146 | { |
2815f817 CF |
147 | struct interface *ifp; |
148 | struct prefix dst_ip; | |
149 | int status; | |
150 | ||
9beff0bd PG |
151 | ifp = bfd_get_peer_info(zclient->ibuf, &dst_ip, NULL, &status, |
152 | NULL, vrf_id); | |
e782cca7 | 153 | if (!ifp || (dst_ip.family != AF_INET && dst_ip.family != AF_INET6)) |
2815f817 CF |
154 | return 0; |
155 | ||
156 | if (isis->debugs & DEBUG_BFD) { | |
157 | char dst_buf[INET6_ADDRSTRLEN]; | |
5489eb45 | 158 | |
e782cca7 RW |
159 | inet_ntop(dst_ip.family, &dst_ip.u.prefix, dst_buf, |
160 | sizeof(dst_buf)); | |
2815f817 CF |
161 | |
162 | zlog_debug("ISIS-BFD: Received update for %s on %s: Changed state to %s", | |
163 | dst_buf, ifp->name, bfd_get_status_str(status)); | |
164 | } | |
165 | ||
a5eba4e9 | 166 | struct isis_circuit *circuit = circuit_scan_by_ifp(ifp); |
5489eb45 | 167 | |
a5eba4e9 CF |
168 | if (!circuit) |
169 | return 0; | |
170 | ||
171 | if (circuit->circ_type == CIRCUIT_T_BROADCAST) { | |
172 | for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { | |
173 | struct list *adjdb = circuit->u.bc.adjdb[level - 1]; | |
174 | ||
175 | struct listnode *node, *nnode; | |
176 | struct isis_adjacency *adj; | |
177 | ||
178 | for (ALL_LIST_ELEMENTS(adjdb, node, nnode, adj)) | |
179 | bfd_adj_event(adj, &dst_ip, status); | |
180 | } | |
181 | } else if (circuit->circ_type == CIRCUIT_T_P2P) { | |
182 | if (circuit->u.p2p.neighbor) { | |
183 | bfd_adj_event(circuit->u.p2p.neighbor, | |
184 | &dst_ip, status); | |
185 | } | |
186 | } | |
187 | ||
52df8228 CF |
188 | return 0; |
189 | } | |
190 | ||
121f9dee | 191 | static int isis_bfd_nbr_replay(ZAPI_CALLBACK_ARGS) |
52df8228 | 192 | { |
0945d5ed | 193 | bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, vrf_id); |
ab880eaf CF |
194 | |
195 | struct listnode *anode; | |
196 | struct isis_area *area; | |
197 | ||
2815f817 CF |
198 | if (isis->debugs & DEBUG_BFD) |
199 | zlog_debug("ISIS-BFD: Got neighbor replay request, resending neighbors."); | |
200 | ||
ab880eaf CF |
201 | for (ALL_LIST_ELEMENTS_RO(isis->area_list, anode, area)) { |
202 | struct listnode *cnode; | |
203 | struct isis_circuit *circuit; | |
204 | ||
205 | for (ALL_LIST_ELEMENTS_RO(area->circuit_list, cnode, circuit)) | |
206 | isis_bfd_circuit_cmd(circuit, ZEBRA_BFD_DEST_UPDATE); | |
207 | } | |
208 | ||
2815f817 CF |
209 | if (isis->debugs & DEBUG_BFD) |
210 | zlog_debug("ISIS-BFD: Done with replay."); | |
211 | ||
52df8228 CF |
212 | return 0; |
213 | } | |
214 | ||
215 | static void (*orig_zebra_connected)(struct zclient *); | |
216 | static void isis_bfd_zebra_connected(struct zclient *zclient) | |
217 | { | |
218 | if (orig_zebra_connected) | |
219 | orig_zebra_connected(zclient); | |
220 | ||
0945d5ed | 221 | bfd_client_sendmsg(zclient, ZEBRA_BFD_CLIENT_REGISTER, VRF_DEFAULT); |
52df8228 CF |
222 | } |
223 | ||
e782cca7 | 224 | static void bfd_debug(int family, union g_addr *dst, union g_addr *src, |
2815f817 CF |
225 | const char *interface, int command) |
226 | { | |
227 | if (!(isis->debugs & DEBUG_BFD)) | |
228 | return; | |
229 | ||
230 | char dst_str[INET6_ADDRSTRLEN]; | |
231 | char src_str[INET6_ADDRSTRLEN]; | |
232 | ||
e782cca7 RW |
233 | inet_ntop(family, dst, dst_str, sizeof(dst_str)); |
234 | inet_ntop(family, src, src_str, sizeof(src_str)); | |
2815f817 CF |
235 | |
236 | const char *command_str; | |
237 | ||
238 | switch (command) { | |
239 | case ZEBRA_BFD_DEST_REGISTER: | |
240 | command_str = "Register"; | |
241 | break; | |
242 | case ZEBRA_BFD_DEST_DEREGISTER: | |
243 | command_str = "Deregister"; | |
244 | break; | |
245 | case ZEBRA_BFD_DEST_UPDATE: | |
246 | command_str = "Update"; | |
247 | break; | |
248 | default: | |
249 | command_str = "Unknown-Cmd"; | |
250 | break; | |
251 | } | |
252 | ||
253 | zlog_debug("ISIS-BFD: %s peer %s on %s (src %s)", | |
254 | command_str, dst_str, interface, src_str); | |
255 | } | |
256 | ||
20a42f01 CF |
257 | static void bfd_handle_adj_down(struct isis_adjacency *adj) |
258 | { | |
259 | if (!adj->bfd_session) | |
260 | return; | |
261 | ||
e782cca7 RW |
262 | bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip, |
263 | &adj->bfd_session->src_ip, adj->circuit->interface->name, | |
264 | ZEBRA_BFD_DEST_DEREGISTER); | |
2815f817 | 265 | |
e782cca7 | 266 | bfd_peer_sendmsg(zclient, NULL, adj->bfd_session->family, |
20a42f01 CF |
267 | &adj->bfd_session->dst_ip, |
268 | &adj->bfd_session->src_ip, | |
269 | adj->circuit->interface->name, | |
270 | 0, /* ttl */ | |
271 | 0, /* multihop */ | |
9beff0bd | 272 | 1, /* control plane independent bit is on */ |
20a42f01 CF |
273 | ZEBRA_BFD_DEST_DEREGISTER, |
274 | 0, /* set_flag */ | |
275 | VRF_DEFAULT); | |
276 | bfd_session_free(&adj->bfd_session); | |
277 | } | |
278 | ||
279 | static void bfd_handle_adj_up(struct isis_adjacency *adj, int command) | |
215eccb0 | 280 | { |
20a42f01 | 281 | struct isis_circuit *circuit = adj->circuit; |
e782cca7 RW |
282 | int family; |
283 | union g_addr dst_ip; | |
284 | union g_addr src_ip; | |
285 | struct list *local_ips; | |
286 | struct prefix *local_ip; | |
20a42f01 | 287 | |
e782cca7 | 288 | if (!circuit->bfd_info) |
20a42f01 CF |
289 | goto out; |
290 | ||
e782cca7 RW |
291 | /* |
292 | * If IS-IS is enabled for both IPv4 and IPv6 on the circuit, prefer | |
293 | * creating a BFD session over IPv6. | |
294 | */ | |
295 | if (circuit->ipv6_router && adj->ipv6_address_count) { | |
296 | family = AF_INET6; | |
297 | dst_ip.ipv6 = adj->ipv6_addresses[0]; | |
298 | local_ips = circuit->ipv6_link; | |
299 | if (!local_ips || list_isempty(local_ips)) | |
300 | goto out; | |
301 | local_ip = listgetdata(listhead(local_ips)); | |
302 | src_ip.ipv6 = local_ip->u.prefix6; | |
303 | } else if (circuit->ip_router && adj->ipv4_address_count) { | |
304 | family = AF_INET; | |
305 | dst_ip.ipv4 = adj->ipv4_addresses[0]; | |
306 | local_ips = fabricd_ip_addrs(adj->circuit); | |
307 | if (!local_ips || list_isempty(local_ips)) | |
308 | goto out; | |
309 | local_ip = listgetdata(listhead(local_ips)); | |
310 | src_ip.ipv4 = local_ip->u.prefix4; | |
311 | } else | |
20a42f01 CF |
312 | goto out; |
313 | ||
20a42f01 | 314 | if (adj->bfd_session) { |
e782cca7 RW |
315 | if (bfd_session_same(adj->bfd_session, family, &src_ip, |
316 | &dst_ip)) | |
20a42f01 CF |
317 | bfd_handle_adj_down(adj); |
318 | } | |
319 | ||
320 | if (!adj->bfd_session) | |
e782cca7 | 321 | adj->bfd_session = bfd_session_new(family, &dst_ip, &src_ip); |
20a42f01 | 322 | |
e782cca7 RW |
323 | bfd_debug(adj->bfd_session->family, &adj->bfd_session->dst_ip, |
324 | &adj->bfd_session->src_ip, circuit->interface->name, command); | |
325 | bfd_peer_sendmsg(zclient, circuit->bfd_info, adj->bfd_session->family, | |
20a42f01 CF |
326 | &adj->bfd_session->dst_ip, |
327 | &adj->bfd_session->src_ip, | |
328 | circuit->interface->name, | |
329 | 0, /* ttl */ | |
330 | 0, /* multihop */ | |
9beff0bd | 331 | 1, /* control plane independent bit is on */ |
20a42f01 CF |
332 | command, |
333 | 0, /* set flag */ | |
334 | VRF_DEFAULT); | |
215eccb0 | 335 | return; |
20a42f01 CF |
336 | out: |
337 | bfd_handle_adj_down(adj); | |
338 | } | |
339 | ||
340 | static int bfd_handle_adj_state_change(struct isis_adjacency *adj) | |
341 | { | |
342 | if (adj->adj_state == ISIS_ADJ_UP) | |
343 | bfd_handle_adj_up(adj, ZEBRA_BFD_DEST_REGISTER); | |
344 | else | |
345 | bfd_handle_adj_down(adj); | |
346 | return 0; | |
347 | } | |
348 | ||
349 | static void bfd_adj_cmd(struct isis_adjacency *adj, int command) | |
350 | { | |
351 | if (adj->adj_state == ISIS_ADJ_UP | |
352 | && command != ZEBRA_BFD_DEST_DEREGISTER) { | |
353 | bfd_handle_adj_up(adj, command); | |
354 | } else { | |
355 | bfd_handle_adj_down(adj); | |
356 | } | |
357 | } | |
358 | ||
359 | void isis_bfd_circuit_cmd(struct isis_circuit *circuit, int command) | |
360 | { | |
361 | switch (circuit->circ_type) { | |
362 | case CIRCUIT_T_BROADCAST: | |
363 | for (int level = ISIS_LEVEL1; level <= ISIS_LEVEL2; level++) { | |
364 | struct list *adjdb = circuit->u.bc.adjdb[level - 1]; | |
365 | ||
366 | struct listnode *node; | |
367 | struct isis_adjacency *adj; | |
5489eb45 | 368 | |
20a42f01 CF |
369 | for (ALL_LIST_ELEMENTS_RO(adjdb, node, adj)) |
370 | bfd_adj_cmd(adj, command); | |
371 | } | |
372 | break; | |
373 | case CIRCUIT_T_P2P: | |
374 | if (circuit->u.p2p.neighbor) | |
375 | bfd_adj_cmd(circuit->u.p2p.neighbor, command); | |
376 | break; | |
377 | default: | |
378 | break; | |
379 | } | |
215eccb0 CF |
380 | } |
381 | ||
382 | void isis_bfd_circuit_param_set(struct isis_circuit *circuit, | |
383 | uint32_t min_rx, uint32_t min_tx, | |
384 | uint32_t detect_mult, int defaults) | |
385 | { | |
386 | int command = 0; | |
387 | ||
388 | bfd_set_param(&circuit->bfd_info, min_rx, | |
389 | min_tx, detect_mult, defaults, &command); | |
390 | ||
391 | if (command) | |
392 | isis_bfd_circuit_cmd(circuit, command); | |
393 | } | |
394 | ||
3015e3d1 CF |
395 | static int bfd_circuit_write_settings(struct isis_circuit *circuit, |
396 | struct vty *vty) | |
397 | { | |
398 | struct bfd_info *bfd_info = circuit->bfd_info; | |
399 | ||
400 | if (!bfd_info) | |
401 | return 0; | |
402 | ||
490a6fc7 | 403 | vty_out(vty, " %s bfd\n", PROTO_NAME); |
3015e3d1 CF |
404 | return 1; |
405 | } | |
406 | ||
52df8228 CF |
407 | void isis_bfd_init(void) |
408 | { | |
409 | bfd_gbl_init(); | |
410 | ||
411 | orig_zebra_connected = zclient->zebra_connected; | |
412 | zclient->zebra_connected = isis_bfd_zebra_connected; | |
413 | zclient->interface_bfd_dest_update = isis_bfd_interface_dest_update; | |
414 | zclient->bfd_dest_replay = isis_bfd_nbr_replay; | |
20a42f01 CF |
415 | hook_register(isis_adj_state_change_hook, |
416 | bfd_handle_adj_state_change); | |
3015e3d1 CF |
417 | hook_register(isis_circuit_config_write, |
418 | bfd_circuit_write_settings); | |
52df8228 | 419 | } |