/src/frr/pimd/pim_jp_agg.c
Line | Count | Source (jump to first uncovered line) |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * PIM for FRR - J/P Aggregation |
4 | | * Copyright (C) 2017 Cumulus Networks, Inc. |
5 | | * Donald Sharp |
6 | | */ |
7 | | #include <zebra.h> |
8 | | |
9 | | #include "linklist.h" |
10 | | #include "log.h" |
11 | | #include "vrf.h" |
12 | | #include "if.h" |
13 | | |
14 | | #include "pimd.h" |
15 | | #include "pim_instance.h" |
16 | | #include "pim_msg.h" |
17 | | #include "pim_jp_agg.h" |
18 | | #include "pim_join.h" |
19 | | #include "pim_iface.h" |
20 | | |
21 | | void pim_jp_agg_group_list_free(struct pim_jp_agg_group *jag) |
22 | 0 | { |
23 | 0 | list_delete(&jag->sources); |
24 | |
|
25 | 0 | XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); |
26 | 0 | } |
27 | | |
28 | | static void pim_jp_agg_src_free(struct pim_jp_sources *js) |
29 | 0 | { |
30 | 0 | struct pim_upstream *up = js->up; |
31 | | |
32 | | /* |
33 | | * When we are being called here, we know |
34 | | * that the neighbor is going away start |
35 | | * the normal j/p timer so that it can |
36 | | * pick this shit back up when the |
37 | | * nbr comes back alive |
38 | | */ |
39 | 0 | if (up) |
40 | 0 | join_timer_start(js->up); |
41 | 0 | XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); |
42 | 0 | } |
43 | | |
44 | | int pim_jp_agg_group_list_cmp(void *arg1, void *arg2) |
45 | 0 | { |
46 | 0 | const struct pim_jp_agg_group *jag1 = |
47 | 0 | (const struct pim_jp_agg_group *)arg1; |
48 | 0 | const struct pim_jp_agg_group *jag2 = |
49 | 0 | (const struct pim_jp_agg_group *)arg2; |
50 | |
|
51 | 0 | return pim_addr_cmp(jag1->group, jag2->group); |
52 | 0 | } |
53 | | |
54 | | static int pim_jp_agg_src_cmp(void *arg1, void *arg2) |
55 | 0 | { |
56 | 0 | const struct pim_jp_sources *js1 = (const struct pim_jp_sources *)arg1; |
57 | 0 | const struct pim_jp_sources *js2 = (const struct pim_jp_sources *)arg2; |
58 | |
|
59 | 0 | if (js1->is_join && !js2->is_join) |
60 | 0 | return -1; |
61 | | |
62 | 0 | if (!js1->is_join && js2->is_join) |
63 | 0 | return 1; |
64 | | |
65 | 0 | return pim_addr_cmp(js1->up->sg.src, js2->up->sg.src); |
66 | 0 | } |
67 | | |
68 | | /* |
69 | | * This function is used by scan_oil to clear |
70 | | * the created jp_agg_group created when |
71 | | * figuring out where to send prunes |
72 | | * and joins. |
73 | | */ |
74 | | void pim_jp_agg_clear_group(struct list *group) |
75 | 0 | { |
76 | 0 | struct listnode *gnode, *gnnode; |
77 | 0 | struct listnode *snode, *snnode; |
78 | 0 | struct pim_jp_agg_group *jag; |
79 | 0 | struct pim_jp_sources *js; |
80 | |
|
81 | 0 | for (ALL_LIST_ELEMENTS(group, gnode, gnnode, jag)) { |
82 | 0 | for (ALL_LIST_ELEMENTS(jag->sources, snode, snnode, js)) { |
83 | 0 | listnode_delete(jag->sources, js); |
84 | 0 | js->up = NULL; |
85 | 0 | XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); |
86 | 0 | } |
87 | 0 | list_delete(&jag->sources); |
88 | 0 | listnode_delete(group, jag); |
89 | 0 | XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); |
90 | 0 | } |
91 | 0 | } |
92 | | |
93 | | static struct pim_iface_upstream_switch * |
94 | | pim_jp_agg_get_interface_upstream_switch_list(struct pim_rpf *rpf) |
95 | 0 | { |
96 | 0 | struct interface *ifp = rpf->source_nexthop.interface; |
97 | 0 | struct pim_interface *pim_ifp; |
98 | 0 | struct pim_iface_upstream_switch *pius; |
99 | 0 | struct listnode *node, *nnode; |
100 | |
|
101 | 0 | if (!ifp) |
102 | 0 | return NULL; |
103 | | |
104 | 0 | pim_ifp = ifp->info; |
105 | | |
106 | | /* Old interface is pim disabled */ |
107 | 0 | if (!pim_ifp) |
108 | 0 | return NULL; |
109 | | |
110 | 0 | for (ALL_LIST_ELEMENTS(pim_ifp->upstream_switch_list, node, nnode, |
111 | 0 | pius)) { |
112 | 0 | if (!pim_addr_cmp(pius->address, rpf->rpf_addr)) |
113 | 0 | break; |
114 | 0 | } |
115 | | |
116 | 0 | if (!pius) { |
117 | 0 | pius = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, |
118 | 0 | sizeof(struct pim_iface_upstream_switch)); |
119 | 0 | pius->address = rpf->rpf_addr; |
120 | 0 | pius->us = list_new(); |
121 | 0 | listnode_add_sort(pim_ifp->upstream_switch_list, pius); |
122 | 0 | } |
123 | |
|
124 | 0 | return pius; |
125 | 0 | } |
126 | | |
127 | | void pim_jp_agg_remove_group(struct list *group, struct pim_upstream *up, |
128 | | struct pim_neighbor *nbr) |
129 | 0 | { |
130 | 0 | struct listnode *node, *nnode; |
131 | 0 | struct pim_jp_agg_group *jag = NULL; |
132 | 0 | struct pim_jp_sources *js = NULL; |
133 | |
|
134 | 0 | for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { |
135 | 0 | if (!pim_addr_cmp(jag->group, up->sg.grp)) |
136 | 0 | break; |
137 | 0 | } |
138 | | |
139 | 0 | if (!jag) |
140 | 0 | return; |
141 | | |
142 | 0 | for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { |
143 | 0 | if (js->up == up) |
144 | 0 | break; |
145 | 0 | } |
146 | | |
147 | 0 | if (nbr) { |
148 | 0 | if (PIM_DEBUG_TRACE) |
149 | 0 | zlog_debug("up %s remove from nbr %s/%pPAs jp-agg-list", |
150 | 0 | up->sg_str, nbr->interface->name, |
151 | 0 | &nbr->source_addr); |
152 | 0 | } |
153 | |
|
154 | 0 | if (js) { |
155 | 0 | js->up = NULL; |
156 | 0 | listnode_delete(jag->sources, js); |
157 | 0 | XFREE(MTYPE_PIM_JP_AGG_SOURCE, js); |
158 | 0 | } |
159 | |
|
160 | 0 | if (jag->sources->count == 0) { |
161 | 0 | list_delete(&jag->sources); |
162 | 0 | listnode_delete(group, jag); |
163 | 0 | XFREE(MTYPE_PIM_JP_AGG_GROUP, jag); |
164 | 0 | } |
165 | 0 | } |
166 | | |
167 | | int pim_jp_agg_is_in_list(struct list *group, struct pim_upstream *up) |
168 | 0 | { |
169 | 0 | struct listnode *node, *nnode; |
170 | 0 | struct pim_jp_agg_group *jag = NULL; |
171 | 0 | struct pim_jp_sources *js = NULL; |
172 | |
|
173 | 0 | for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { |
174 | 0 | if (!pim_addr_cmp(jag->group, up->sg.grp)) |
175 | 0 | break; |
176 | 0 | } |
177 | | |
178 | 0 | if (!jag) |
179 | 0 | return 0; |
180 | | |
181 | 0 | for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { |
182 | 0 | if (js->up == up) |
183 | 0 | return 1; |
184 | 0 | } |
185 | | |
186 | 0 | return 0; |
187 | 0 | } |
188 | | |
189 | | //#define PIM_JP_AGG_DEBUG 1 |
190 | | /* |
191 | | * For the given upstream, check all the neighbor |
192 | | * jp_agg lists and ensure that it is not |
193 | | * in another list |
194 | | * |
195 | | * *IF* ignore is true we can skip |
196 | | * up->rpf.source_nexthop.interface particular interface for checking |
197 | | * |
198 | | * This is a debugging function, Probably |
199 | | * can be safely compiled out in real |
200 | | * builds |
201 | | */ |
202 | | void pim_jp_agg_upstream_verification(struct pim_upstream *up, bool ignore) |
203 | 112k | { |
204 | | #ifdef PIM_JP_AGG_DEBUG |
205 | | struct interface *ifp; |
206 | | struct pim_interface *pim_ifp; |
207 | | struct pim_instance *pim; |
208 | | |
209 | | if (!up->rpf.source_nexthop.interface) { |
210 | | if (PIM_DEBUG_PIM_TRACE) |
211 | | zlog_debug("%s: up %s RPF is not present", __func__, |
212 | | up->sg_str); |
213 | | return; |
214 | | } |
215 | | |
216 | | pim_ifp = up->rpf.source_nexthop.interface->info; |
217 | | pim = pim_ifp->pim; |
218 | | |
219 | | FOR_ALL_INTERFACES (pim->vrf, ifp) { |
220 | | pim_ifp = ifp->info; |
221 | | struct listnode *nnode; |
222 | | |
223 | | if (ignore && ifp == up->rpf.source_nexthop.interface) |
224 | | continue; |
225 | | |
226 | | if (pim_ifp) { |
227 | | struct pim_neighbor *neigh; |
228 | | for (ALL_LIST_ELEMENTS_RO(pim_ifp->pim_neighbor_list, |
229 | | nnode, neigh)) { |
230 | | assert(!pim_jp_agg_is_in_list( |
231 | | neigh->upstream_jp_agg, up)); |
232 | | } |
233 | | } |
234 | | } |
235 | | #else |
236 | 112k | return; |
237 | 112k | #endif |
238 | 112k | } |
239 | | |
240 | | void pim_jp_agg_add_group(struct list *group, struct pim_upstream *up, |
241 | | bool is_join, struct pim_neighbor *nbr) |
242 | 0 | { |
243 | 0 | struct listnode *node, *nnode; |
244 | 0 | struct pim_jp_agg_group *jag = NULL; |
245 | 0 | struct pim_jp_sources *js = NULL; |
246 | |
|
247 | 0 | for (ALL_LIST_ELEMENTS(group, node, nnode, jag)) { |
248 | 0 | if (!pim_addr_cmp(jag->group, up->sg.grp)) |
249 | 0 | break; |
250 | 0 | } |
251 | | |
252 | 0 | if (!jag) { |
253 | 0 | jag = XCALLOC(MTYPE_PIM_JP_AGG_GROUP, |
254 | 0 | sizeof(struct pim_jp_agg_group)); |
255 | 0 | jag->group = up->sg.grp; |
256 | 0 | jag->sources = list_new(); |
257 | 0 | jag->sources->cmp = pim_jp_agg_src_cmp; |
258 | 0 | jag->sources->del = (void (*)(void *))pim_jp_agg_src_free; |
259 | 0 | listnode_add_sort(group, jag); |
260 | 0 | } |
261 | |
|
262 | 0 | for (ALL_LIST_ELEMENTS(jag->sources, node, nnode, js)) { |
263 | 0 | if (js->up == up) |
264 | 0 | break; |
265 | 0 | } |
266 | | |
267 | 0 | if (nbr) { |
268 | 0 | if (PIM_DEBUG_TRACE) |
269 | 0 | zlog_debug("up %s add to nbr %s/%pPAs jp-agg-list", |
270 | 0 | up->sg_str, |
271 | 0 | up->rpf.source_nexthop.interface->name, |
272 | 0 | &nbr->source_addr); |
273 | 0 | } |
274 | |
|
275 | 0 | if (!js) { |
276 | 0 | js = XCALLOC(MTYPE_PIM_JP_AGG_SOURCE, |
277 | 0 | sizeof(struct pim_jp_sources)); |
278 | 0 | js->up = up; |
279 | 0 | js->is_join = is_join; |
280 | 0 | listnode_add_sort(jag->sources, js); |
281 | 0 | } else { |
282 | 0 | if (js->is_join != is_join) { |
283 | 0 | listnode_delete(jag->sources, js); |
284 | 0 | js->is_join = is_join; |
285 | 0 | listnode_add_sort(jag->sources, js); |
286 | 0 | } |
287 | 0 | } |
288 | 0 | } |
289 | | |
290 | | void pim_jp_agg_switch_interface(struct pim_rpf *orpf, struct pim_rpf *nrpf, |
291 | | struct pim_upstream *up) |
292 | 0 | { |
293 | 0 | struct pim_iface_upstream_switch *opius; |
294 | 0 | struct pim_iface_upstream_switch *npius; |
295 | |
|
296 | 0 | opius = pim_jp_agg_get_interface_upstream_switch_list(orpf); |
297 | 0 | npius = pim_jp_agg_get_interface_upstream_switch_list(nrpf); |
298 | | |
299 | | /* |
300 | | * RFC 4601: 4.5.7. Sending (S,G) Join/Prune Messages |
301 | | * |
302 | | * Transitions from Joined State |
303 | | * |
304 | | * RPF'(S,G) changes not due to an Assert |
305 | | * |
306 | | * The upstream (S,G) state machine remains in Joined |
307 | | * state. Send Join(S,G) to the new upstream neighbor, which is |
308 | | * the new value of RPF'(S,G). Send Prune(S,G) to the old |
309 | | * upstream neighbor, which is the old value of RPF'(S,G). Set |
310 | | * the Join Timer (JT) to expire after t_periodic seconds. |
311 | | */ |
312 | | |
313 | | /* send Prune(S,G) to the old upstream neighbor */ |
314 | 0 | if (opius) |
315 | 0 | pim_jp_agg_add_group(opius->us, up, false, NULL); |
316 | | |
317 | | /* send Join(S,G) to the current upstream neighbor */ |
318 | 0 | if (npius) |
319 | 0 | pim_jp_agg_add_group(npius->us, up, true, NULL); |
320 | 0 | } |
321 | | |
322 | | |
323 | | void pim_jp_agg_single_upstream_send(struct pim_rpf *rpf, |
324 | | struct pim_upstream *up, bool is_join) |
325 | 535 | { |
326 | 535 | struct list groups, sources; |
327 | 535 | struct pim_jp_agg_group jag; |
328 | 535 | struct pim_jp_sources js; |
329 | | |
330 | | /* skip JP upstream messages if source is directly connected */ |
331 | 535 | if (!up || !rpf->source_nexthop.interface || |
332 | 535 | pim_if_connected_to_source(rpf->source_nexthop.interface, |
333 | 0 | up->sg.src) || |
334 | 535 | if_is_loopback(rpf->source_nexthop.interface)) |
335 | 535 | return; |
336 | | |
337 | 0 | memset(&groups, 0, sizeof(groups)); |
338 | 0 | memset(&sources, 0, sizeof(sources)); |
339 | 0 | jag.sources = &sources; |
340 | |
|
341 | 0 | listnode_add(&groups, &jag); |
342 | 0 | listnode_add(jag.sources, &js); |
343 | |
|
344 | 0 | jag.group = up->sg.grp; |
345 | 0 | js.up = up; |
346 | 0 | js.is_join = is_join; |
347 | |
|
348 | 0 | pim_joinprune_send(rpf, &groups); |
349 | |
|
350 | 0 | list_delete_all_node(jag.sources); |
351 | 0 | list_delete_all_node(&groups); |
352 | 0 | } |