]> git.proxmox.com Git - mirror_frr.git/blob - lib/routemap_northbound.c
Merge pull request #6293 from GalaxyGorilla/json_diff
[mirror_frr.git] / lib / routemap_northbound.c
1 /*
2 * Route map northbound implementation.
3 *
4 * Copyright (C) 2019 Network Device Education Foundation, Inc. ("NetDEF")
5 * Rafael Zalamena
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
20 * 02110-1301 USA.
21 */
22
23 #include <zebra.h>
24
25 #include "lib/command.h"
26 #include "lib/log.h"
27 #include "lib/northbound.h"
28 #include "lib/routemap.h"
29
30 /*
31 * Auxiliary functions to avoid code duplication:
32 *
33 * lib_route_map_entry_set_destroy: unset `set` commands.
34 * lib_route_map_entry_match_destroy: unset `match` commands.
35 */
36 int lib_route_map_entry_match_destroy(struct nb_cb_destroy_args *args)
37 {
38 struct routemap_hook_context *rhc;
39 int rv;
40
41 if (args->event != NB_EV_APPLY)
42 return NB_OK;
43
44 rhc = nb_running_get_entry(args->dnode, NULL, true);
45 if (rhc->rhc_mhook == NULL)
46 return NB_OK;
47
48 rv = rhc->rhc_mhook(NULL, rhc->rhc_rmi, rhc->rhc_rule, NULL,
49 rhc->rhc_event);
50 if (rv != CMD_SUCCESS)
51 return NB_ERR_INCONSISTENCY;
52
53 return NB_OK;
54 }
55
56 int lib_route_map_entry_set_destroy(struct nb_cb_destroy_args *args)
57 {
58 struct routemap_hook_context *rhc;
59 int rv;
60
61 if (args->event != NB_EV_APPLY)
62 return NB_OK;
63
64 rhc = nb_running_get_entry(args->dnode, NULL, true);
65 if (rhc->rhc_shook == NULL)
66 return NB_OK;
67
68 rv = rhc->rhc_shook(NULL, rhc->rhc_rmi, rhc->rhc_rule, NULL);
69 if (rv != CMD_SUCCESS)
70 return NB_ERR_INCONSISTENCY;
71
72 return NB_OK;
73 }
74
75 /*
76 * Auxiliary hook context list manipulation functions.
77 */
78 struct routemap_hook_context *
79 routemap_hook_context_insert(struct route_map_index *rmi)
80 {
81 struct routemap_hook_context *rhc;
82
83 rhc = XCALLOC(MTYPE_TMP, sizeof(*rhc));
84 rhc->rhc_rmi = rmi;
85 TAILQ_INSERT_TAIL(&rmi->rhclist, rhc, rhc_entry);
86
87 return rhc;
88 }
89
90 void routemap_hook_context_free(struct routemap_hook_context *rhc)
91 {
92 struct route_map_index *rmi = rhc->rhc_rmi;
93
94 TAILQ_REMOVE(&rmi->rhclist, rhc, rhc_entry);
95 XFREE(MTYPE_TMP, rhc);
96 }
97
98 /*
99 * XPath: /frr-route-map:lib/route-map
100 */
101 static int lib_route_map_create(struct nb_cb_create_args *args)
102 {
103 struct route_map *rm;
104 const char *rm_name;
105
106 switch (args->event) {
107 case NB_EV_VALIDATE:
108 case NB_EV_PREPARE:
109 case NB_EV_ABORT:
110 /* NOTHING */
111 break;
112 case NB_EV_APPLY:
113 rm_name = yang_dnode_get_string(args->dnode, "./name");
114 rm = route_map_get(rm_name);
115 nb_running_set_entry(args->dnode, rm);
116 break;
117 }
118
119 return NB_OK;
120 }
121
122 static int lib_route_map_destroy(struct nb_cb_destroy_args *args)
123 {
124 struct route_map *rm;
125
126 switch (args->event) {
127 case NB_EV_VALIDATE:
128 case NB_EV_PREPARE:
129 case NB_EV_ABORT:
130 /* NOTHING */
131 break;
132 case NB_EV_APPLY:
133 rm = nb_running_unset_entry(args->dnode);
134 route_map_delete(rm);
135 break;
136 }
137
138 return NB_OK;
139 }
140
141 /*
142 * XPath: /frr-route-map:lib/route-map/entry
143 */
144 static int lib_route_map_entry_create(struct nb_cb_create_args *args)
145 {
146 struct route_map_index *rmi;
147 struct route_map *rm;
148 uint16_t sequence;
149 int action;
150
151 switch (args->event) {
152 case NB_EV_VALIDATE:
153 case NB_EV_PREPARE:
154 case NB_EV_ABORT:
155 /* NOTHING */
156 break;
157 case NB_EV_APPLY:
158 sequence = yang_dnode_get_uint16(args->dnode, "./sequence");
159 action = yang_dnode_get_enum(args->dnode, "./action") == 0
160 ? RMAP_PERMIT
161 : RMAP_DENY;
162 rm = nb_running_get_entry(args->dnode, NULL, true);
163 rmi = route_map_index_get(rm, action, sequence);
164 nb_running_set_entry(args->dnode, rmi);
165 break;
166 }
167
168 return NB_OK;
169 }
170
171 static int lib_route_map_entry_destroy(struct nb_cb_destroy_args *args)
172 {
173 struct route_map_index *rmi;
174
175 switch (args->event) {
176 case NB_EV_VALIDATE:
177 case NB_EV_PREPARE:
178 case NB_EV_ABORT:
179 /* NOTHING */
180 break;
181 case NB_EV_APPLY:
182 rmi = nb_running_unset_entry(args->dnode);
183 route_map_index_delete(rmi, 1);
184 break;
185 }
186
187 return NB_OK;
188 }
189
190 /*
191 * XPath: /frr-route-map:lib/route-map/entry/description
192 */
193 static int
194 lib_route_map_entry_description_modify(struct nb_cb_modify_args *args)
195 {
196 struct route_map_index *rmi;
197 const char *description;
198
199 switch (args->event) {
200 case NB_EV_VALIDATE:
201 /* NOTHING */
202 break;
203 case NB_EV_PREPARE:
204 description = yang_dnode_get_string(args->dnode, NULL);
205 args->resource->ptr = XSTRDUP(MTYPE_TMP, description);
206 if (args->resource->ptr == NULL)
207 return NB_ERR_RESOURCE;
208 break;
209 case NB_EV_ABORT:
210 XFREE(MTYPE_TMP, args->resource->ptr);
211 break;
212 case NB_EV_APPLY:
213 rmi = nb_running_get_entry(args->dnode, NULL, true);
214 XFREE(MTYPE_TMP, rmi->description);
215 rmi->description = args->resource->ptr;
216 break;
217 }
218
219 return NB_OK;
220 }
221
222 static int
223 lib_route_map_entry_description_destroy(struct nb_cb_destroy_args *args)
224 {
225 struct route_map_index *rmi;
226
227 switch (args->event) {
228 case NB_EV_VALIDATE:
229 case NB_EV_PREPARE:
230 case NB_EV_ABORT:
231 /* NOTHING */
232 break;
233 case NB_EV_APPLY:
234 rmi = nb_running_get_entry(args->dnode, NULL, true);
235 XFREE(MTYPE_TMP, rmi->description);
236 break;
237 }
238
239 return NB_OK;
240 }
241
242 /*
243 * XPath: /frr-route-map:lib/route-map/entry/action
244 */
245 static int lib_route_map_entry_action_modify(struct nb_cb_modify_args *args)
246 {
247 struct route_map_index *rmi;
248
249 switch (args->event) {
250 case NB_EV_VALIDATE:
251 case NB_EV_PREPARE:
252 case NB_EV_ABORT:
253 /* NOTHING */
254 break;
255 case NB_EV_APPLY:
256 rmi = nb_running_get_entry(args->dnode, NULL, true);
257 rmi->type = yang_dnode_get_enum(args->dnode, NULL);
258 /* TODO: notify? */
259 break;
260 }
261
262 return NB_OK;
263 }
264
265 /*
266 * XPath: /frr-route-map:lib/route-map/entry/call
267 */
268 static int lib_route_map_entry_call_modify(struct nb_cb_modify_args *args)
269 {
270 struct route_map_index *rmi;
271 const char *rm_name, *rmn_name;
272
273 switch (args->event) {
274 case NB_EV_VALIDATE:
275 rm_name = yang_dnode_get_string(args->dnode, "../../name");
276 rmn_name = yang_dnode_get_string(args->dnode, NULL);
277 /* Don't allow to jump to the same route map instance. */
278 if (strcmp(rm_name, rmn_name) == 0)
279 return NB_ERR_VALIDATION;
280
281 /* TODO: detect circular route map sequences. */
282 break;
283 case NB_EV_PREPARE:
284 rmn_name = yang_dnode_get_string(args->dnode, NULL);
285 args->resource->ptr = XSTRDUP(MTYPE_ROUTE_MAP_NAME, rmn_name);
286 break;
287 case NB_EV_ABORT:
288 XFREE(MTYPE_ROUTE_MAP_NAME, args->resource->ptr);
289 break;
290 case NB_EV_APPLY:
291 rmi = nb_running_get_entry(args->dnode, NULL, true);
292 if (rmi->nextrm) {
293 route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED,
294 rmi->nextrm, rmi->map->name);
295 XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm);
296 }
297 rmi->nextrm = args->resource->ptr;
298 route_map_upd8_dependency(RMAP_EVENT_CALL_ADDED, rmi->nextrm,
299 rmi->map->name);
300 break;
301 }
302
303 return NB_OK;
304 }
305
306 static int lib_route_map_entry_call_destroy(struct nb_cb_destroy_args *args)
307 {
308 struct route_map_index *rmi;
309
310 switch (args->event) {
311 case NB_EV_VALIDATE:
312 case NB_EV_PREPARE:
313 case NB_EV_ABORT:
314 /* NOTHING */
315 break;
316 case NB_EV_APPLY:
317 rmi = nb_running_get_entry(args->dnode, NULL, true);
318 route_map_upd8_dependency(RMAP_EVENT_CALL_DELETED, rmi->nextrm,
319 rmi->map->name);
320 XFREE(MTYPE_ROUTE_MAP_NAME, rmi->nextrm);
321 rmi->nextrm = NULL;
322 break;
323 }
324
325 return NB_OK;
326 }
327
328 /*
329 * XPath: /frr-route-map:lib/route-map/entry/exit-policy
330 */
331 static int
332 lib_route_map_entry_exit_policy_modify(struct nb_cb_modify_args *args)
333 {
334 struct route_map_index *rmi;
335 int rm_action;
336 int policy;
337
338 switch (args->event) {
339 case NB_EV_VALIDATE:
340 policy = yang_dnode_get_enum(args->dnode, NULL);
341 switch (policy) {
342 case 0: /* permit-or-deny */
343 break;
344 case 1: /* next */
345 /* FALLTHROUGH */
346 case 2: /* goto */
347 rm_action =
348 yang_dnode_get_enum(args->dnode, "../action");
349 if (rm_action == 1 /* deny */) {
350 /*
351 * On deny it is not possible to 'goto'
352 * anywhere.
353 */
354 return NB_ERR_VALIDATION;
355 }
356 break;
357 }
358 break;
359 case NB_EV_PREPARE:
360 case NB_EV_ABORT:
361 break;
362 case NB_EV_APPLY:
363 rmi = nb_running_get_entry(args->dnode, NULL, true);
364 policy = yang_dnode_get_enum(args->dnode, NULL);
365
366 switch (policy) {
367 case 0: /* permit-or-deny */
368 rmi->exitpolicy = RMAP_EXIT;
369 break;
370 case 1: /* next */
371 rmi->exitpolicy = RMAP_NEXT;
372 break;
373 case 2: /* goto */
374 rmi->exitpolicy = RMAP_GOTO;
375 break;
376 }
377 break;
378 }
379
380 return NB_OK;
381 }
382
383 /*
384 * XPath: /frr-route-map:lib/route-map/entry/goto-value
385 */
386 static int lib_route_map_entry_goto_value_modify(struct nb_cb_modify_args *args)
387 {
388 struct route_map_index *rmi;
389 uint16_t rmi_index;
390 uint16_t rmi_next;
391
392 switch (args->event) {
393 case NB_EV_VALIDATE:
394 rmi_index = yang_dnode_get_uint16(args->dnode, "../sequence");
395 rmi_next = yang_dnode_get_uint16(args->dnode, NULL);
396 if (rmi_next <= rmi_index) {
397 /* Can't jump backwards on a route map. */
398 return NB_ERR_VALIDATION;
399 }
400 break;
401 case NB_EV_PREPARE:
402 case NB_EV_ABORT:
403 /* NOTHING */
404 break;
405 case NB_EV_APPLY:
406 rmi = nb_running_get_entry(args->dnode, NULL, true);
407 rmi->nextpref = yang_dnode_get_uint16(args->dnode, NULL);
408 break;
409 }
410
411 return NB_OK;
412 }
413
414 static int
415 lib_route_map_entry_goto_value_destroy(struct nb_cb_destroy_args *args)
416 {
417 struct route_map_index *rmi;
418
419 switch (args->event) {
420 case NB_EV_VALIDATE:
421 case NB_EV_PREPARE:
422 case NB_EV_ABORT:
423 /* NOTHING */
424 break;
425 case NB_EV_APPLY:
426 rmi = nb_running_get_entry(args->dnode, NULL, true);
427 rmi->nextpref = 0;
428 break;
429 }
430
431 return NB_OK;
432 }
433
434 /*
435 * XPath: /frr-route-map:lib/route-map/entry/match-condition
436 */
437 static int
438 lib_route_map_entry_match_condition_create(struct nb_cb_create_args *args)
439 {
440 struct routemap_hook_context *rhc;
441 struct route_map_index *rmi;
442
443 switch (args->event) {
444 case NB_EV_VALIDATE:
445 case NB_EV_PREPARE:
446 case NB_EV_ABORT:
447 /* NOTHING */
448 break;
449 case NB_EV_APPLY:
450 rmi = nb_running_get_entry(args->dnode, NULL, true);
451 rhc = routemap_hook_context_insert(rmi);
452 nb_running_set_entry(args->dnode, rhc);
453 break;
454 }
455
456 return NB_OK;
457 }
458
459 static int
460 lib_route_map_entry_match_condition_destroy(struct nb_cb_destroy_args *args)
461 {
462 struct routemap_hook_context *rhc;
463 int rv;
464
465 if (args->event != NB_EV_APPLY)
466 return NB_OK;
467
468 rv = lib_route_map_entry_match_destroy(args);
469 rhc = nb_running_unset_entry(args->dnode);
470 routemap_hook_context_free(rhc);
471
472 return rv;
473 }
474
475 /*
476 * XPath: /frr-route-map:lib/route-map/entry/match-condition/interface
477 */
478 static int lib_route_map_entry_match_condition_interface_modify(
479 struct nb_cb_modify_args *args)
480 {
481 struct routemap_hook_context *rhc;
482 const char *ifname;
483 int rv;
484
485 if (args->event != NB_EV_APPLY)
486 return NB_OK;
487
488 /* Check for hook function. */
489 if (rmap_match_set_hook.match_interface == NULL)
490 return NB_OK;
491
492 /* Add configuration. */
493 rhc = nb_running_get_entry(args->dnode, NULL, true);
494 ifname = yang_dnode_get_string(args->dnode, NULL);
495
496 /* Set destroy information. */
497 rhc->rhc_mhook = rmap_match_set_hook.no_match_interface;
498 rhc->rhc_rule = "interface";
499 rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
500
501 rv = rmap_match_set_hook.match_interface(NULL, rhc->rhc_rmi,
502 "interface", ifname,
503 RMAP_EVENT_MATCH_ADDED);
504 if (rv != CMD_SUCCESS) {
505 rhc->rhc_mhook = NULL;
506 return NB_ERR_INCONSISTENCY;
507 }
508
509 return NB_OK;
510 }
511
512 static int lib_route_map_entry_match_condition_interface_destroy(
513 struct nb_cb_destroy_args *args)
514 {
515 return lib_route_map_entry_match_destroy(args);
516 }
517
518 /*
519 * XPath: /frr-route-map:lib/route-map/entry/match-condition/access-list-num
520 */
521 static int lib_route_map_entry_match_condition_access_list_num_modify(
522 struct nb_cb_modify_args *args)
523 {
524 struct routemap_hook_context *rhc;
525 const char *acl;
526 int condition, rv;
527
528 if (args->event != NB_EV_APPLY)
529 return NB_OK;
530
531 /* Check for hook function. */
532 rv = CMD_SUCCESS;
533 acl = yang_dnode_get_string(args->dnode, NULL);
534 rhc = nb_running_get_entry(args->dnode, NULL, true);
535 condition = yang_dnode_get_enum(args->dnode, "../condition");
536 switch (condition) {
537 case 1: /* ipv4-address-list */
538 if (rmap_match_set_hook.match_ip_address == NULL)
539 break;
540 rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_address;
541 rhc->rhc_rule = "ip address";
542 rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
543 rv = rmap_match_set_hook.match_ip_address(
544 NULL, rhc->rhc_rmi, "ip address", acl,
545 RMAP_EVENT_FILTER_ADDED);
546 break;
547 case 3: /* ipv4-next-hop-list */
548 if (rmap_match_set_hook.match_ip_next_hop == NULL)
549 break;
550 rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop;
551 rhc->rhc_rule = "ip next-hop";
552 rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
553 rv = rmap_match_set_hook.match_ip_next_hop(
554 NULL, rhc->rhc_rmi, "ip next-hop", acl,
555 RMAP_EVENT_FILTER_ADDED);
556 break;
557 }
558 if (rv != CMD_SUCCESS) {
559 rhc->rhc_mhook = NULL;
560 return NB_ERR_INCONSISTENCY;
561 }
562
563 return NB_OK;
564 }
565
566 static int lib_route_map_entry_match_condition_access_list_num_destroy(
567 struct nb_cb_destroy_args *args)
568 {
569 return lib_route_map_entry_match_destroy(args);
570 }
571
572 /*
573 * XPath:
574 * /frr-route-map:lib/route-map/entry/match-condition/access-list-num-extended
575 */
576 static int lib_route_map_entry_match_condition_access_list_num_extended_modify(
577 struct nb_cb_modify_args *args)
578 {
579 return lib_route_map_entry_match_condition_access_list_num_modify(args);
580 }
581
582 static int lib_route_map_entry_match_condition_access_list_num_extended_destroy(
583 struct nb_cb_destroy_args *args)
584 {
585 return lib_route_map_entry_match_condition_access_list_num_destroy(
586 args);
587 }
588
589 /*
590 * XPath: /frr-route-map:lib/route-map/entry/match-condition/list-name
591 */
592 static int lib_route_map_entry_match_condition_list_name_modify(
593 struct nb_cb_modify_args *args)
594 {
595 struct routemap_hook_context *rhc;
596 const char *acl;
597 int condition;
598 int rv;
599
600 if (args->event != NB_EV_APPLY)
601 return NB_OK;
602
603 /* Check for hook installation, otherwise we can just stop. */
604 acl = yang_dnode_get_string(args->dnode, NULL);
605 rhc = nb_running_get_entry(args->dnode, NULL, true);
606 condition = yang_dnode_get_enum(args->dnode, "../condition");
607 switch (condition) {
608 case 1: /* ipv4-address-list */
609 if (rmap_match_set_hook.match_ip_address == NULL)
610 return NB_OK;
611 rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_address;
612 rhc->rhc_rule = "ip address";
613 rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
614 rv = rmap_match_set_hook.match_ip_address(
615 NULL, rhc->rhc_rmi, "ip address", acl,
616 RMAP_EVENT_FILTER_ADDED);
617 break;
618 case 2: /* ipv4-prefix-list */
619 if (rmap_match_set_hook.match_ip_address_prefix_list == NULL)
620 return NB_OK;
621 rhc->rhc_mhook =
622 rmap_match_set_hook.no_match_ip_address_prefix_list;
623 rhc->rhc_rule = "ip address prefix-list";
624 rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
625 rv = rmap_match_set_hook.match_ip_address_prefix_list(
626 NULL, rhc->rhc_rmi, "ip address prefix-list", acl,
627 RMAP_EVENT_PLIST_ADDED);
628 break;
629 case 3: /* ipv4-next-hop-list */
630 if (rmap_match_set_hook.match_ip_next_hop == NULL)
631 return NB_OK;
632 rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop;
633 rhc->rhc_rule = "ip next-hop";
634 rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
635 rv = rmap_match_set_hook.match_ip_next_hop(
636 NULL, rhc->rhc_rmi, "ip next-hop", acl,
637 RMAP_EVENT_FILTER_ADDED);
638 break;
639 case 4: /* ipv4-next-hop-prefix-list */
640 if (rmap_match_set_hook.match_ip_next_hop_prefix_list == NULL)
641 return NB_OK;
642 rhc->rhc_mhook =
643 rmap_match_set_hook.no_match_ip_next_hop_prefix_list;
644 rhc->rhc_rule = "ip next-hop prefix-list";
645 rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
646 rv = rmap_match_set_hook.match_ip_next_hop_prefix_list(
647 NULL, rhc->rhc_rmi, "ip next-hop prefix-list", acl,
648 RMAP_EVENT_PLIST_ADDED);
649 break;
650 case 6: /* ipv6-address-list */
651 if (rmap_match_set_hook.match_ipv6_address == NULL)
652 return NB_OK;
653 rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_address;
654 rhc->rhc_rule = "ipv6 address";
655 rhc->rhc_event = RMAP_EVENT_FILTER_DELETED;
656 rv = rmap_match_set_hook.match_ipv6_address(
657 NULL, rhc->rhc_rmi, "ipv6 address", acl,
658 RMAP_EVENT_FILTER_ADDED);
659 break;
660 case 7: /* ipv6-prefix-list */
661 if (rmap_match_set_hook.match_ipv6_address_prefix_list == NULL)
662 return NB_OK;
663 rhc->rhc_mhook =
664 rmap_match_set_hook.no_match_ipv6_address_prefix_list;
665 rhc->rhc_rule = "ipv6 address prefix-list";
666 rhc->rhc_event = RMAP_EVENT_PLIST_DELETED;
667 rv = rmap_match_set_hook.match_ipv6_address_prefix_list(
668 NULL, rhc->rhc_rmi, "ipv6 address prefix-list", acl,
669 RMAP_EVENT_PLIST_ADDED);
670 break;
671 default:
672 rv = CMD_ERR_NO_MATCH;
673 break;
674 }
675 if (rv != CMD_SUCCESS) {
676 rhc->rhc_mhook = NULL;
677 return NB_ERR_INCONSISTENCY;
678 }
679
680 return NB_OK;
681 }
682
683 static int lib_route_map_entry_match_condition_list_name_destroy(
684 struct nb_cb_destroy_args *args)
685 {
686 return lib_route_map_entry_match_destroy(args);
687 }
688
689 /*
690 * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv4-next-hop-type
691 */
692 static int lib_route_map_entry_match_condition_ipv4_next_hop_type_modify(
693 struct nb_cb_modify_args *args)
694 {
695 struct routemap_hook_context *rhc;
696 const char *type;
697 int rv;
698
699 if (args->event != NB_EV_APPLY)
700 return NB_OK;
701
702 /* Check for hook function. */
703 if (rmap_match_set_hook.match_ip_next_hop_type == NULL)
704 return NB_OK;
705
706 /* Add configuration. */
707 rhc = nb_running_get_entry(args->dnode, NULL, true);
708 type = yang_dnode_get_string(args->dnode, NULL);
709
710 /* Set destroy information. */
711 rhc->rhc_mhook = rmap_match_set_hook.no_match_ip_next_hop_type;
712 rhc->rhc_rule = "ip next-hop type";
713 rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
714
715 rv = rmap_match_set_hook.match_ip_next_hop_type(
716 NULL, rhc->rhc_rmi, "ip next-hop type", type,
717 RMAP_EVENT_MATCH_ADDED);
718 if (rv != CMD_SUCCESS) {
719 rhc->rhc_mhook = NULL;
720 return NB_ERR_INCONSISTENCY;
721 }
722
723 return NB_OK;
724 }
725
726 static int lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy(
727 struct nb_cb_destroy_args *args)
728 {
729 return lib_route_map_entry_match_destroy(args);
730 }
731
732 /*
733 * XPath: /frr-route-map:lib/route-map/entry/match-condition/ipv6-next-hop-type
734 */
735 static int lib_route_map_entry_match_condition_ipv6_next_hop_type_modify(
736 struct nb_cb_modify_args *args)
737 {
738 struct routemap_hook_context *rhc;
739 const char *type;
740 int rv;
741
742 if (args->event != NB_EV_APPLY)
743 return NB_OK;
744
745 /* Check for hook function. */
746 if (rmap_match_set_hook.match_ipv6_next_hop_type == NULL)
747 return NB_OK;
748
749 /* Add configuration. */
750 rhc = nb_running_get_entry(args->dnode, NULL, true);
751 type = yang_dnode_get_string(args->dnode, NULL);
752
753 /* Set destroy information. */
754 rhc->rhc_mhook = rmap_match_set_hook.no_match_ipv6_next_hop_type;
755 rhc->rhc_rule = "ipv6 next-hop type";
756 rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
757
758 rv = rmap_match_set_hook.match_ipv6_next_hop_type(
759 NULL, rhc->rhc_rmi, "ipv6 next-hop type", type,
760 RMAP_EVENT_MATCH_ADDED);
761 if (rv != CMD_SUCCESS) {
762 rhc->rhc_mhook = NULL;
763 return NB_ERR_INCONSISTENCY;
764 }
765
766 return NB_OK;
767 }
768
769 static int lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy(
770 struct nb_cb_destroy_args *args)
771 {
772 return lib_route_map_entry_match_destroy(args);
773 }
774
775 /*
776 * XPath: /frr-route-map:lib/route-map/entry/match-condition/metric
777 */
778 static int lib_route_map_entry_match_condition_metric_modify(
779 struct nb_cb_modify_args *args)
780 {
781 struct routemap_hook_context *rhc;
782 const char *type;
783 int rv;
784
785 if (args->event != NB_EV_APPLY)
786 return NB_OK;
787
788 /* Check for hook function. */
789 if (rmap_match_set_hook.match_metric == NULL)
790 return NB_OK;
791
792 /* Add configuration. */
793 rhc = nb_running_get_entry(args->dnode, NULL, true);
794 type = yang_dnode_get_string(args->dnode, NULL);
795
796 /* Set destroy information. */
797 rhc->rhc_mhook = rmap_match_set_hook.no_match_metric;
798 rhc->rhc_rule = "metric";
799 rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
800
801 rv = rmap_match_set_hook.match_metric(NULL, rhc->rhc_rmi, "metric",
802 type, RMAP_EVENT_MATCH_ADDED);
803 if (rv != CMD_SUCCESS) {
804 rhc->rhc_mhook = NULL;
805 return NB_ERR_INCONSISTENCY;
806 }
807
808 return NB_OK;
809 }
810
811 static int lib_route_map_entry_match_condition_metric_destroy(
812 struct nb_cb_destroy_args *args)
813 {
814 return lib_route_map_entry_match_destroy(args);
815 }
816
817 /*
818 * XPath: /frr-route-map:lib/route-map/entry/match-condition/tag
819 */
820 static int
821 lib_route_map_entry_match_condition_tag_modify(struct nb_cb_modify_args *args)
822 {
823 struct routemap_hook_context *rhc;
824 const char *tag;
825 int rv;
826
827 if (args->event != NB_EV_APPLY)
828 return NB_OK;
829
830 /* Check for hook function. */
831 if (rmap_match_set_hook.match_tag == NULL)
832 return NB_OK;
833
834 /* Add configuration. */
835 rhc = nb_running_get_entry(args->dnode, NULL, true);
836 tag = yang_dnode_get_string(args->dnode, NULL);
837
838 /* Set destroy information. */
839 rhc->rhc_mhook = rmap_match_set_hook.no_match_tag;
840 rhc->rhc_rule = "tag";
841 rhc->rhc_event = RMAP_EVENT_MATCH_DELETED;
842
843 rv = rmap_match_set_hook.match_tag(NULL, rhc->rhc_rmi, "tag", tag,
844 RMAP_EVENT_MATCH_ADDED);
845 if (rv != CMD_SUCCESS) {
846 rhc->rhc_mhook = NULL;
847 return NB_ERR_INCONSISTENCY;
848 }
849
850 return NB_OK;
851 }
852
853 static int
854 lib_route_map_entry_match_condition_tag_destroy(struct nb_cb_destroy_args *args)
855 {
856 return lib_route_map_entry_match_destroy(args);
857 }
858
859 /*
860 * XPath: /frr-route-map:lib/route-map/entry/set-action
861 */
862 static int lib_route_map_entry_set_action_create(struct nb_cb_create_args *args)
863 {
864 return lib_route_map_entry_match_condition_create(args);
865 }
866
867 static int
868 lib_route_map_entry_set_action_destroy(struct nb_cb_destroy_args *args)
869 {
870 struct routemap_hook_context *rhc;
871 int rv;
872
873 if (args->event != NB_EV_APPLY)
874 return NB_OK;
875
876 rv = lib_route_map_entry_set_destroy(args);
877 rhc = nb_running_unset_entry(args->dnode);
878 routemap_hook_context_free(rhc);
879
880 return rv;
881 }
882
883 /*
884 * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv4-address
885 */
886 static int lib_route_map_entry_set_action_ipv4_address_modify(
887 struct nb_cb_modify_args *args)
888 {
889 struct routemap_hook_context *rhc;
890 const char *address;
891 struct in_addr ia;
892 int rv;
893
894 switch (args->event) {
895 case NB_EV_VALIDATE:
896 /*
897 * NOTE: validate if 'action' is 'ipv4-next-hop',
898 * currently it is not necessary because this is the
899 * only implemented action.
900 */
901 yang_dnode_get_ipv4(&ia, args->dnode, NULL);
902 if (ia.s_addr == INADDR_ANY || IPV4_CLASS_DE(ntohl(ia.s_addr)))
903 return NB_ERR_VALIDATION;
904 /* FALLTHROUGH */
905 case NB_EV_PREPARE:
906 case NB_EV_ABORT:
907 return NB_OK;
908 case NB_EV_APPLY:
909 break;
910 }
911
912 /* Check for hook function. */
913 if (rmap_match_set_hook.set_ip_nexthop == NULL)
914 return NB_OK;
915
916 /* Add configuration. */
917 rhc = nb_running_get_entry(args->dnode, NULL, true);
918 address = yang_dnode_get_string(args->dnode, NULL);
919
920 /* Set destroy information. */
921 rhc->rhc_shook = rmap_match_set_hook.no_set_ip_nexthop;
922 rhc->rhc_rule = "ip next-hop";
923
924 rv = rmap_match_set_hook.set_ip_nexthop(NULL, rhc->rhc_rmi,
925 "ip next-hop", address);
926 if (rv != CMD_SUCCESS) {
927 rhc->rhc_shook = NULL;
928 return NB_ERR_INCONSISTENCY;
929 }
930
931 return NB_OK;
932 }
933
934 static int lib_route_map_entry_set_action_ipv4_address_destroy(
935 struct nb_cb_destroy_args *args)
936 {
937 return lib_route_map_entry_set_destroy(args);
938 }
939
940 /*
941 * XPath: /frr-route-map:lib/route-map/entry/set-action/ipv6-address
942 */
943 static int lib_route_map_entry_set_action_ipv6_address_modify(
944 struct nb_cb_modify_args *args)
945 {
946 struct routemap_hook_context *rhc;
947 const char *address;
948 struct in6_addr i6a;
949 int rv;
950
951 switch (args->event) {
952 case NB_EV_VALIDATE:
953 /*
954 * NOTE: validate if 'action' is 'ipv6-next-hop',
955 * currently it is not necessary because this is the
956 * only implemented action. Other actions might have
957 * different validations.
958 */
959 yang_dnode_get_ipv6(&i6a, args->dnode, NULL);
960 if (!IN6_IS_ADDR_LINKLOCAL(&i6a))
961 return NB_ERR_VALIDATION;
962 /* FALLTHROUGH */
963 case NB_EV_PREPARE:
964 case NB_EV_ABORT:
965 return NB_OK;
966 case NB_EV_APPLY:
967 break;
968 }
969
970 /* Check for hook function. */
971 if (rmap_match_set_hook.set_ipv6_nexthop_local == NULL)
972 return NB_OK;
973
974 /* Add configuration. */
975 rhc = nb_running_get_entry(args->dnode, NULL, true);
976 address = yang_dnode_get_string(args->dnode, NULL);
977
978 /* Set destroy information. */
979 rhc->rhc_shook = rmap_match_set_hook.no_set_ipv6_nexthop_local;
980 rhc->rhc_rule = "ipv6 next-hop local";
981
982 rv = rmap_match_set_hook.set_ipv6_nexthop_local(
983 NULL, rhc->rhc_rmi, "ipv6 next-hop local", address);
984 if (rv != CMD_SUCCESS) {
985 rhc->rhc_shook = NULL;
986 return NB_ERR_INCONSISTENCY;
987 }
988
989 return NB_OK;
990 }
991
992 static int lib_route_map_entry_set_action_ipv6_address_destroy(
993 struct nb_cb_destroy_args *args)
994 {
995 return lib_route_map_entry_set_destroy(args);
996 }
997
998 /*
999 * XPath: /frr-route-map:lib/route-map/entry/set-action/value
1000 */
1001 static int set_action_modify(enum nb_event event, const struct lyd_node *dnode,
1002 union nb_resource *resource, const char *value)
1003 {
1004 struct routemap_hook_context *rhc;
1005 int rv;
1006
1007 /*
1008 * NOTE: validate if 'action' is 'metric', currently it is not
1009 * necessary because this is the only implemented action. Other
1010 * actions might have different validations.
1011 */
1012 if (event != NB_EV_APPLY)
1013 return NB_OK;
1014
1015 /* Check for hook function. */
1016 if (rmap_match_set_hook.set_metric == NULL)
1017 return NB_OK;
1018
1019 /* Add configuration. */
1020 rhc = nb_running_get_entry(dnode, NULL, true);
1021
1022 /* Set destroy information. */
1023 rhc->rhc_shook = rmap_match_set_hook.no_set_metric;
1024 rhc->rhc_rule = "metric";
1025
1026 rv = rmap_match_set_hook.set_metric(NULL, rhc->rhc_rmi, "metric",
1027 value);
1028 if (rv != CMD_SUCCESS) {
1029 rhc->rhc_shook = NULL;
1030 return NB_ERR_INCONSISTENCY;
1031 }
1032
1033 return NB_OK;
1034 }
1035
1036 static int
1037 lib_route_map_entry_set_action_value_modify(struct nb_cb_modify_args *args)
1038 {
1039 const char *metric = yang_dnode_get_string(args->dnode, NULL);
1040
1041 return set_action_modify(args->event, args->dnode, args->resource,
1042 metric);
1043 }
1044
1045 static int
1046 lib_route_map_entry_set_action_value_destroy(struct nb_cb_destroy_args *args)
1047 {
1048 return lib_route_map_entry_set_destroy(args);
1049 }
1050
1051 /*
1052 * XPath: /frr-route-map:lib/route-map/entry/set-action/add-metric
1053 */
1054 static int
1055 lib_route_map_entry_set_action_add_metric_modify(struct nb_cb_modify_args *args)
1056 {
1057 return set_action_modify(args->event, args->dnode, args->resource,
1058 "+metric");
1059 }
1060
1061 static int lib_route_map_entry_set_action_add_metric_destroy(
1062 struct nb_cb_destroy_args *args)
1063 {
1064 return lib_route_map_entry_set_action_value_destroy(args);
1065 }
1066
1067 /*
1068 * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-metric
1069 */
1070 static int lib_route_map_entry_set_action_subtract_metric_modify(
1071 struct nb_cb_modify_args *args)
1072 {
1073 return set_action_modify(args->event, args->dnode, args->resource,
1074 "-metric");
1075 }
1076
1077 static int lib_route_map_entry_set_action_subtract_metric_destroy(
1078 struct nb_cb_destroy_args *args)
1079 {
1080 return lib_route_map_entry_set_action_value_destroy(args);
1081 }
1082
1083 /*
1084 * XPath: /frr-route-map:lib/route-map/entry/set-action/use-round-trip-time
1085 */
1086 static int lib_route_map_entry_set_action_use_round_trip_time_modify(
1087 struct nb_cb_modify_args *args)
1088 {
1089 return set_action_modify(args->event, args->dnode, args->resource,
1090 "rtt");
1091 }
1092
1093 static int lib_route_map_entry_set_action_use_round_trip_time_destroy(
1094 struct nb_cb_destroy_args *args)
1095 {
1096 return lib_route_map_entry_set_action_value_destroy(args);
1097 }
1098
1099 /*
1100 * XPath: /frr-route-map:lib/route-map/entry/set-action/add-round-trip-time
1101 */
1102 static int lib_route_map_entry_set_action_add_round_trip_time_modify(
1103 struct nb_cb_modify_args *args)
1104 {
1105 return set_action_modify(args->event, args->dnode, args->resource,
1106 "+rtt");
1107 }
1108
1109 static int lib_route_map_entry_set_action_add_round_trip_time_destroy(
1110 struct nb_cb_destroy_args *args)
1111 {
1112 return lib_route_map_entry_set_action_value_destroy(args);
1113 }
1114
1115 /*
1116 * XPath: /frr-route-map:lib/route-map/entry/set-action/subtract-round-trip-time
1117 */
1118 static int lib_route_map_entry_set_action_subtract_round_trip_time_modify(
1119 struct nb_cb_modify_args *args)
1120 {
1121 return set_action_modify(args->event, args->dnode, args->resource,
1122 "-rtt");
1123 }
1124
1125 static int lib_route_map_entry_set_action_subtract_round_trip_time_destroy(
1126 struct nb_cb_destroy_args *args)
1127 {
1128 return lib_route_map_entry_set_action_value_destroy(args);
1129 }
1130
1131 /*
1132 * XPath: /frr-route-map:lib/route-map/entry/set-action/tag
1133 */
1134 static int
1135 lib_route_map_entry_set_action_tag_modify(struct nb_cb_modify_args *args)
1136 {
1137 struct routemap_hook_context *rhc;
1138 const char *tag;
1139 int rv;
1140
1141 /*
1142 * NOTE: validate if 'action' is 'tag', currently it is not
1143 * necessary because this is the only implemented action. Other
1144 * actions might have different validations.
1145 */
1146 if (args->event != NB_EV_APPLY)
1147 return NB_OK;
1148
1149 /* Check for hook function. */
1150 if (rmap_match_set_hook.set_tag == NULL)
1151 return NB_OK;
1152
1153 /* Add configuration. */
1154 rhc = nb_running_get_entry(args->dnode, NULL, true);
1155 tag = yang_dnode_get_string(args->dnode, NULL);
1156
1157 /* Set destroy information. */
1158 rhc->rhc_shook = rmap_match_set_hook.no_set_tag;
1159 rhc->rhc_rule = "tag";
1160
1161 rv = rmap_match_set_hook.set_tag(NULL, rhc->rhc_rmi, "tag", tag);
1162 if (rv != CMD_SUCCESS) {
1163 rhc->rhc_shook = NULL;
1164 return NB_ERR_INCONSISTENCY;
1165 }
1166
1167 return NB_OK;
1168 }
1169
1170 static int
1171 lib_route_map_entry_set_action_tag_destroy(struct nb_cb_destroy_args *args)
1172 {
1173 return lib_route_map_entry_set_destroy(args);
1174 }
1175
1176 /* clang-format off */
1177 const struct frr_yang_module_info frr_route_map_info = {
1178 .name = "frr-route-map",
1179 .nodes = {
1180 {
1181 .xpath = "/frr-route-map:lib/route-map",
1182 .cbs = {
1183 .create = lib_route_map_create,
1184 .destroy = lib_route_map_destroy,
1185 }
1186 },
1187 {
1188 .xpath = "/frr-route-map:lib/route-map/entry",
1189 .cbs = {
1190 .create = lib_route_map_entry_create,
1191 .destroy = lib_route_map_entry_destroy,
1192 .cli_show = route_map_instance_show,
1193 .cli_show_end = route_map_instance_show_end,
1194 }
1195 },
1196 {
1197 .xpath = "/frr-route-map:lib/route-map/entry/description",
1198 .cbs = {
1199 .modify = lib_route_map_entry_description_modify,
1200 .destroy = lib_route_map_entry_description_destroy,
1201 .cli_show = route_map_description_show,
1202 }
1203 },
1204 {
1205 .xpath = "/frr-route-map:lib/route-map/entry/action",
1206 .cbs = {
1207 .modify = lib_route_map_entry_action_modify,
1208 }
1209 },
1210 {
1211 .xpath = "/frr-route-map:lib/route-map/entry/call",
1212 .cbs = {
1213 .modify = lib_route_map_entry_call_modify,
1214 .destroy = lib_route_map_entry_call_destroy,
1215 .cli_show = route_map_call_show,
1216 }
1217 },
1218 {
1219 .xpath = "/frr-route-map:lib/route-map/entry/exit-policy",
1220 .cbs = {
1221 .modify = lib_route_map_entry_exit_policy_modify,
1222 .cli_show = route_map_exit_policy_show,
1223 }
1224 },
1225 {
1226 .xpath = "/frr-route-map:lib/route-map/entry/goto-value",
1227 .cbs = {
1228 .modify = lib_route_map_entry_goto_value_modify,
1229 .destroy = lib_route_map_entry_goto_value_destroy,
1230 }
1231 },
1232 {
1233 .xpath = "/frr-route-map:lib/route-map/entry/match-condition",
1234 .cbs = {
1235 .create = lib_route_map_entry_match_condition_create,
1236 .destroy = lib_route_map_entry_match_condition_destroy,
1237 .cli_show = route_map_condition_show,
1238 }
1239 },
1240 {
1241 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/interface",
1242 .cbs = {
1243 .modify = lib_route_map_entry_match_condition_interface_modify,
1244 .destroy = lib_route_map_entry_match_condition_interface_destroy,
1245 }
1246 },
1247 {
1248 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/access-list-num",
1249 .cbs = {
1250 .modify = lib_route_map_entry_match_condition_access_list_num_modify,
1251 .destroy = lib_route_map_entry_match_condition_access_list_num_destroy,
1252 }
1253 },
1254 {
1255 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/access-list-num-extended",
1256 .cbs = {
1257 .modify = lib_route_map_entry_match_condition_access_list_num_extended_modify,
1258 .destroy = lib_route_map_entry_match_condition_access_list_num_extended_destroy,
1259 }
1260 },
1261 {
1262 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/list-name",
1263 .cbs = {
1264 .modify = lib_route_map_entry_match_condition_list_name_modify,
1265 .destroy = lib_route_map_entry_match_condition_list_name_destroy,
1266 }
1267 },
1268 {
1269 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/ipv4-next-hop-type",
1270 .cbs = {
1271 .modify = lib_route_map_entry_match_condition_ipv4_next_hop_type_modify,
1272 .destroy = lib_route_map_entry_match_condition_ipv4_next_hop_type_destroy,
1273 }
1274 },
1275 {
1276 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/ipv6-next-hop-type",
1277 .cbs = {
1278 .modify = lib_route_map_entry_match_condition_ipv6_next_hop_type_modify,
1279 .destroy = lib_route_map_entry_match_condition_ipv6_next_hop_type_destroy,
1280 }
1281 },
1282 {
1283 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/metric",
1284 .cbs = {
1285 .modify = lib_route_map_entry_match_condition_metric_modify,
1286 .destroy = lib_route_map_entry_match_condition_metric_destroy,
1287 }
1288 },
1289 {
1290 .xpath = "/frr-route-map:lib/route-map/entry/match-condition/tag",
1291 .cbs = {
1292 .modify = lib_route_map_entry_match_condition_tag_modify,
1293 .destroy = lib_route_map_entry_match_condition_tag_destroy,
1294 }
1295 },
1296 {
1297 .xpath = "/frr-route-map:lib/route-map/entry/set-action",
1298 .cbs = {
1299 .create = lib_route_map_entry_set_action_create,
1300 .destroy = lib_route_map_entry_set_action_destroy,
1301 .cli_show = route_map_action_show,
1302 }
1303 },
1304 {
1305 .xpath = "/frr-route-map:lib/route-map/entry/set-action/ipv4-address",
1306 .cbs = {
1307 .modify = lib_route_map_entry_set_action_ipv4_address_modify,
1308 .destroy = lib_route_map_entry_set_action_ipv4_address_destroy,
1309 }
1310 },
1311 {
1312 .xpath = "/frr-route-map:lib/route-map/entry/set-action/ipv6-address",
1313 .cbs = {
1314 .modify = lib_route_map_entry_set_action_ipv6_address_modify,
1315 .destroy = lib_route_map_entry_set_action_ipv6_address_destroy,
1316 }
1317 },
1318 {
1319 .xpath = "/frr-route-map:lib/route-map/entry/set-action/value",
1320 .cbs = {
1321 .modify = lib_route_map_entry_set_action_value_modify,
1322 .destroy = lib_route_map_entry_set_action_value_destroy,
1323 }
1324 },
1325 {
1326 .xpath = "/frr-route-map:lib/route-map/entry/set-action/add-metric",
1327 .cbs = {
1328 .modify = lib_route_map_entry_set_action_add_metric_modify,
1329 .destroy = lib_route_map_entry_set_action_add_metric_destroy,
1330 }
1331 },
1332 {
1333 .xpath = "/frr-route-map:lib/route-map/entry/set-action/subtract-metric",
1334 .cbs = {
1335 .modify = lib_route_map_entry_set_action_subtract_metric_modify,
1336 .destroy = lib_route_map_entry_set_action_subtract_metric_destroy,
1337 }
1338 },
1339 {
1340 .xpath = "/frr-route-map:lib/route-map/entry/set-action/use-round-trip-time",
1341 .cbs = {
1342 .modify = lib_route_map_entry_set_action_use_round_trip_time_modify,
1343 .destroy = lib_route_map_entry_set_action_use_round_trip_time_destroy,
1344 }
1345 },
1346 {
1347 .xpath = "/frr-route-map:lib/route-map/entry/set-action/add-round-trip-time",
1348 .cbs = {
1349 .modify = lib_route_map_entry_set_action_add_round_trip_time_modify,
1350 .destroy = lib_route_map_entry_set_action_add_round_trip_time_destroy,
1351 }
1352 },
1353 {
1354 .xpath = "/frr-route-map:lib/route-map/entry/set-action/subtract-round-trip-time",
1355 .cbs = {
1356 .modify = lib_route_map_entry_set_action_subtract_round_trip_time_modify,
1357 .destroy = lib_route_map_entry_set_action_subtract_round_trip_time_destroy,
1358 }
1359 },
1360 {
1361 .xpath = "/frr-route-map:lib/route-map/entry/set-action/tag",
1362 .cbs = {
1363 .modify = lib_route_map_entry_set_action_tag_modify,
1364 .destroy = lib_route_map_entry_set_action_tag_destroy,
1365 }
1366 },
1367 {
1368 .xpath = NULL,
1369 },
1370 }
1371 };