]> git.proxmox.com Git - mirror_frr.git/blame - bgpd/bgp_clist.c
Merge pull request #4335 from opensourcerouting/zebra-fpm-blackhole-info
[mirror_frr.git] / bgpd / bgp_clist.c
CommitLineData
718e3744 1/* BGP community-list and extcommunity-list.
896014f4
DL
2 * Copyright (C) 1999 Kunihiro Ishiguro
3 *
4 * This file is part of GNU Zebra.
5 *
6 * GNU Zebra is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License as published by the
8 * Free Software Foundation; either version 2, or (at your option) any
9 * later version.
10 *
11 * GNU Zebra is distributed in the hope that it will be useful, but
12 * WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License along
17 * with this program; see the file COPYING; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
718e3744 20
21#include <zebra.h>
22
23#include "command.h"
24#include "prefix.h"
25#include "memory.h"
3f9c7369 26#include "queue.h"
039f3a34 27#include "filter.h"
937652c6 28#include "stream.h"
3571a6a2 29#include "jhash.h"
718e3744 30
31#include "bgpd/bgpd.h"
32#include "bgpd/bgp_community.h"
33#include "bgpd/bgp_ecommunity.h"
57d187bc 34#include "bgpd/bgp_lcommunity.h"
718e3744 35#include "bgpd/bgp_aspath.h"
36#include "bgpd/bgp_regex.h"
37#include "bgpd/bgp_clist.h"
6b0655a2 38
3571a6a2
DS
39static uint32_t bgp_clist_hash_key_community_list(void *data)
40{
41 struct community_list *cl = data;
42
e237b0d2
DS
43 if (cl->name_hash)
44 return cl->name_hash;
45
46 cl->name_hash = bgp_clist_hash_key(cl->name);
47 return cl->name_hash;
3571a6a2
DS
48}
49
50static bool bgp_clist_hash_cmp_community_list(const void *a1, const void *a2)
51{
52 const struct community_list *cl1 = a1;
53 const struct community_list *cl2 = a2;
54
e237b0d2
DS
55 if (cl1->name_hash != cl2->name_hash)
56 return false;
57
3571a6a2
DS
58 if (strcmp(cl1->name, cl2->name) == 0)
59 return true;
60
61 return false;
62}
63
718e3744 64/* Lookup master structure for community-list or
65 extcommunity-list. */
66struct community_list_master *
d62a17ae 67community_list_master_lookup(struct community_list_handler *ch, int master)
718e3744 68{
d62a17ae 69 if (ch)
70 switch (master) {
71 case COMMUNITY_LIST_MASTER:
72 return &ch->community_list;
73 case EXTCOMMUNITY_LIST_MASTER:
74 return &ch->extcommunity_list;
75 case LARGE_COMMUNITY_LIST_MASTER:
76 return &ch->lcommunity_list;
77 }
78 return NULL;
718e3744 79}
80
81/* Allocate a new community list entry. */
d62a17ae 82static struct community_entry *community_entry_new(void)
718e3744 83{
d62a17ae 84 return XCALLOC(MTYPE_COMMUNITY_LIST_ENTRY,
85 sizeof(struct community_entry));
718e3744 86}
87
88/* Free community list entry. */
d62a17ae 89static void community_entry_free(struct community_entry *entry)
718e3744 90{
d62a17ae 91 switch (entry->style) {
92 case COMMUNITY_LIST_STANDARD:
93 if (entry->u.com)
3c1f53de 94 community_free(&entry->u.com);
d62a17ae 95 break;
96 case LARGE_COMMUNITY_LIST_STANDARD:
97 if (entry->u.lcom)
98 lcommunity_free(&entry->u.lcom);
99 break;
100 case EXTCOMMUNITY_LIST_STANDARD:
101 /* In case of standard extcommunity-list, configuration string
102 is made by ecommunity_ecom2str(). */
0a22ddfb 103 XFREE(MTYPE_ECOMMUNITY_STR, entry->config);
d62a17ae 104 if (entry->u.ecom)
105 ecommunity_free(&entry->u.ecom);
106 break;
107 case COMMUNITY_LIST_EXPANDED:
108 case EXTCOMMUNITY_LIST_EXPANDED:
109 case LARGE_COMMUNITY_LIST_EXPANDED:
0a22ddfb 110 XFREE(MTYPE_COMMUNITY_LIST_CONFIG, entry->config);
d62a17ae 111 if (entry->reg)
112 bgp_regex_free(entry->reg);
113 default:
114 break;
115 }
116 XFREE(MTYPE_COMMUNITY_LIST_ENTRY, entry);
718e3744 117}
118
119/* Allocate a new community-list. */
d62a17ae 120static struct community_list *community_list_new(void)
718e3744 121{
d62a17ae 122 return XCALLOC(MTYPE_COMMUNITY_LIST, sizeof(struct community_list));
718e3744 123}
124
125/* Free community-list. */
d62a17ae 126static void community_list_free(struct community_list *list)
718e3744 127{
0a22ddfb 128 XFREE(MTYPE_COMMUNITY_LIST_NAME, list->name);
d62a17ae 129 XFREE(MTYPE_COMMUNITY_LIST, list);
718e3744 130}
131
94f2b392 132static struct community_list *
d62a17ae 133community_list_insert(struct community_list_handler *ch, const char *name,
134 int master)
718e3744 135{
d62a17ae 136 size_t i;
137 long number;
138 struct community_list *new;
139 struct community_list *point;
140 struct community_list_list *list;
141 struct community_list_master *cm;
142
143 /* Lookup community-list master. */
144 cm = community_list_master_lookup(ch, master);
145 if (!cm)
146 return NULL;
147
148 /* Allocate new community_list and copy given name. */
149 new = community_list_new();
150 new->name = XSTRDUP(MTYPE_COMMUNITY_LIST_NAME, name);
e237b0d2 151 new->name_hash = bgp_clist_hash_key_community_list(new);
d62a17ae 152
3571a6a2
DS
153 /* Save for later */
154 hash_get(cm->hash, new, hash_alloc_intern);
155
d62a17ae 156 /* If name is made by all digit character. We treat it as
157 number. */
158 for (number = 0, i = 0; i < strlen(name); i++) {
159 if (isdigit((int)name[i]))
160 number = (number * 10) + (name[i] - '0');
161 else
162 break;
163 }
718e3744 164
d62a17ae 165 /* In case of name is all digit character */
166 if (i == strlen(name)) {
167 new->sort = COMMUNITY_LIST_NUMBER;
718e3744 168
d62a17ae 169 /* Set access_list to number list. */
170 list = &cm->num;
718e3744 171
d62a17ae 172 for (point = list->head; point; point = point->next)
173 if (atol(point->name) >= number)
174 break;
175 } else {
176 new->sort = COMMUNITY_LIST_STRING;
718e3744 177
d62a17ae 178 /* Set access_list to string list. */
179 list = &cm->str;
718e3744 180
d62a17ae 181 /* Set point to insertion point. */
182 for (point = list->head; point; point = point->next)
183 if (strcmp(point->name, name) >= 0)
184 break;
185 }
718e3744 186
d62a17ae 187 /* Link to upper list. */
188 new->parent = list;
718e3744 189
d62a17ae 190 /* In case of this is the first element of master. */
191 if (list->head == NULL) {
192 list->head = list->tail = new;
193 return new;
194 }
718e3744 195
d62a17ae 196 /* In case of insertion is made at the tail of access_list. */
197 if (point == NULL) {
198 new->prev = list->tail;
199 list->tail->next = new;
200 list->tail = new;
201 return new;
202 }
718e3744 203
d62a17ae 204 /* In case of insertion is made at the head of access_list. */
205 if (point == list->head) {
206 new->next = list->head;
207 list->head->prev = new;
208 list->head = new;
209 return new;
210 }
718e3744 211
d62a17ae 212 /* Insertion is made at middle of the access_list. */
213 new->next = point;
214 new->prev = point->prev;
718e3744 215
d62a17ae 216 if (point->prev)
217 point->prev->next = new;
218 point->prev = new;
718e3744 219
d62a17ae 220 return new;
718e3744 221}
222
d62a17ae 223struct community_list *community_list_lookup(struct community_list_handler *ch,
e237b0d2
DS
224 const char *name,
225 uint32_t name_hash,
226 int master)
718e3744 227{
3571a6a2 228 struct community_list lookup;
d62a17ae 229 struct community_list_master *cm;
718e3744 230
d62a17ae 231 if (!name)
232 return NULL;
718e3744 233
d62a17ae 234 cm = community_list_master_lookup(ch, master);
235 if (!cm)
236 return NULL;
718e3744 237
3571a6a2 238 lookup.name = (char *)name;
e237b0d2 239 lookup.name_hash = name_hash;
3571a6a2 240 return hash_get(cm->hash, &lookup, NULL);
718e3744 241}
242
94f2b392 243static struct community_list *
d62a17ae 244community_list_get(struct community_list_handler *ch, const char *name,
245 int master)
718e3744 246{
d62a17ae 247 struct community_list *list;
718e3744 248
e237b0d2 249 list = community_list_lookup(ch, name, 0, master);
d62a17ae 250 if (!list)
251 list = community_list_insert(ch, name, master);
252 return list;
718e3744 253}
254
3571a6a2
DS
255static void community_list_delete(struct community_list_master *cm,
256 struct community_list *list)
718e3744 257{
d62a17ae 258 struct community_list_list *clist;
259 struct community_entry *entry, *next;
718e3744 260
d62a17ae 261 for (entry = list->head; entry; entry = next) {
262 next = entry->next;
263 community_entry_free(entry);
264 }
718e3744 265
d62a17ae 266 clist = list->parent;
718e3744 267
d62a17ae 268 if (list->next)
269 list->next->prev = list->prev;
270 else
271 clist->tail = list->prev;
718e3744 272
d62a17ae 273 if (list->prev)
274 list->prev->next = list->next;
275 else
276 clist->head = list->next;
718e3744 277
3571a6a2 278 hash_release(cm->hash, list);
d62a17ae 279 community_list_free(list);
718e3744 280}
281
d62a17ae 282static int community_list_empty_p(struct community_list *list)
718e3744 283{
d62a17ae 284 return (list->head == NULL && list->tail == NULL) ? 1 : 0;
718e3744 285}
6b0655a2 286
718e3744 287/* Add community-list entry to the list. */
d62a17ae 288static void community_list_entry_add(struct community_list *list,
289 struct community_entry *entry)
718e3744 290{
d62a17ae 291 entry->next = NULL;
292 entry->prev = list->tail;
293
294 if (list->tail)
295 list->tail->next = entry;
296 else
297 list->head = entry;
298 list->tail = entry;
718e3744 299}
300
301/* Delete community-list entry from the list. */
3571a6a2
DS
302static void community_list_entry_delete(struct community_list_master *cm,
303 struct community_list *list,
3f54c705 304 struct community_entry *entry)
718e3744 305{
d62a17ae 306 if (entry->next)
307 entry->next->prev = entry->prev;
308 else
309 list->tail = entry->prev;
718e3744 310
d62a17ae 311 if (entry->prev)
312 entry->prev->next = entry->next;
313 else
314 list->head = entry->next;
718e3744 315
d62a17ae 316 community_entry_free(entry);
718e3744 317
d62a17ae 318 if (community_list_empty_p(list))
3571a6a2 319 community_list_delete(cm, list);
718e3744 320}
321
322/* Lookup community-list entry from the list. */
323static struct community_entry *
d62a17ae 324community_list_entry_lookup(struct community_list *list, const void *arg,
325 int direct)
718e3744 326{
d62a17ae 327 struct community_entry *entry;
328
329 for (entry = list->head; entry; entry = entry->next) {
330 switch (entry->style) {
331 case COMMUNITY_LIST_STANDARD:
332 if (entry->direct == direct
333 && community_cmp(entry->u.com, arg))
334 return entry;
335 break;
336 case EXTCOMMUNITY_LIST_STANDARD:
337 if (entry->direct == direct
338 && ecommunity_cmp(entry->u.ecom, arg))
339 return entry;
340 break;
341 case LARGE_COMMUNITY_LIST_STANDARD:
342 if (entry->direct == direct
343 && lcommunity_cmp(entry->u.lcom, arg))
344 return entry;
345 break;
346 case COMMUNITY_LIST_EXPANDED:
347 case EXTCOMMUNITY_LIST_EXPANDED:
348 case LARGE_COMMUNITY_LIST_EXPANDED:
349 if (entry->direct == direct
350 && strcmp(entry->config, arg) == 0)
351 return entry;
352 break;
353 default:
354 break;
355 }
356 }
357 return NULL;
718e3744 358}
6b0655a2 359
d62a17ae 360static char *community_str_get(struct community *com, int i)
5cbea288 361{
d7c0a89a
QY
362 uint32_t comval;
363 uint16_t as;
364 uint16_t val;
d62a17ae 365 char *str;
d62a17ae 366
d7c0a89a 367 memcpy(&comval, com_nthval(com, i), sizeof(uint32_t));
d62a17ae 368 comval = ntohl(comval);
369
370 switch (comval) {
371 case COMMUNITY_INTERNET:
aeab4a80 372 str = XSTRDUP(MTYPE_COMMUNITY_STR, "internet");
d62a17ae 373 break;
aa861c10 374 case COMMUNITY_GSHUT:
aeab4a80 375 str = XSTRDUP(MTYPE_COMMUNITY_STR, "graceful-shutdown");
aa861c10
C
376 break;
377 case COMMUNITY_ACCEPT_OWN:
aeab4a80 378 str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own");
aa861c10
C
379 break;
380 case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4:
aeab4a80
QY
381 str = XSTRDUP(MTYPE_COMMUNITY_STR,
382 "route-filter-translated-v4");
aa861c10
C
383 break;
384 case COMMUNITY_ROUTE_FILTER_v4:
aeab4a80 385 str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v4");
aa861c10
C
386 break;
387 case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6:
aeab4a80
QY
388 str = XSTRDUP(MTYPE_COMMUNITY_STR,
389 "route-filter-translated-v6");
aa861c10
C
390 break;
391 case COMMUNITY_ROUTE_FILTER_v6:
aeab4a80 392 str = XSTRDUP(MTYPE_COMMUNITY_STR, "route-filter-v6");
aa861c10
C
393 break;
394 case COMMUNITY_LLGR_STALE:
aeab4a80 395 str = XSTRDUP(MTYPE_COMMUNITY_STR, "llgr-stale");
aa861c10
C
396 break;
397 case COMMUNITY_NO_LLGR:
aeab4a80 398 str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-llgr");
aa861c10
C
399 break;
400 case COMMUNITY_ACCEPT_OWN_NEXTHOP:
aeab4a80 401 str = XSTRDUP(MTYPE_COMMUNITY_STR, "accept-own-nexthop");
aa861c10
C
402 break;
403 case COMMUNITY_BLACKHOLE:
aeab4a80 404 str = XSTRDUP(MTYPE_COMMUNITY_STR, "blackhole");
aa861c10 405 break;
d62a17ae 406 case COMMUNITY_NO_EXPORT:
aeab4a80 407 str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-export");
d62a17ae 408 break;
409 case COMMUNITY_NO_ADVERTISE:
aeab4a80 410 str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-advertise");
d62a17ae 411 break;
412 case COMMUNITY_LOCAL_AS:
aeab4a80 413 str = XSTRDUP(MTYPE_COMMUNITY_STR, "local-AS");
d62a17ae 414 break;
aa861c10 415 case COMMUNITY_NO_PEER:
aeab4a80 416 str = XSTRDUP(MTYPE_COMMUNITY_STR, "no-peer");
7f323236 417 break;
809d6365 418 default:
aeab4a80 419 str = XSTRDUP(MTYPE_COMMUNITY_STR, "65536:65535");
d62a17ae 420 as = (comval >> 16) & 0xFFFF;
421 val = comval & 0xFFFF;
aeab4a80 422 snprintf(str, strlen(str), "%u:%d", as, val);
d62a17ae 423 break;
424 }
5cbea288 425
d62a17ae 426 return str;
5cbea288
DS
427}
428
429/* Internal function to perform regular expression match for
2acb4ac2 430 * a single community. */
d62a17ae 431static int community_regexp_include(regex_t *reg, struct community *com, int i)
5cbea288 432{
d62a17ae 433 char *str;
434 int rv;
5cbea288 435
d62a17ae 436 /* When there is no communities attribute it is treated as empty string.
437 */
438 if (com == NULL || com->size == 0)
439 str = XSTRDUP(MTYPE_COMMUNITY_STR, "");
440 else
441 str = community_str_get(com, i);
5cbea288 442
d62a17ae 443 /* Regular expression match. */
444 rv = regexec(reg, str, 0, NULL, 0);
b84ee83b 445
d62a17ae 446 XFREE(MTYPE_COMMUNITY_STR, str);
b84ee83b 447
d62a17ae 448 if (rv == 0)
449 return 1;
5cbea288 450
d62a17ae 451 /* No match. */
452 return 0;
5cbea288
DS
453}
454
718e3744 455/* Internal function to perform regular expression match for community
456 attribute. */
d62a17ae 457static int community_regexp_match(struct community *com, regex_t *reg)
718e3744 458{
d62a17ae 459 const char *str;
718e3744 460
d62a17ae 461 /* When there is no communities attribute it is treated as empty
462 string. */
463 if (com == NULL || com->size == 0)
464 str = "";
465 else
a69ea8ae 466 str = community_str(com, false);
718e3744 467
d62a17ae 468 /* Regular expression match. */
469 if (regexec(reg, str, 0, NULL, 0) == 0)
470 return 1;
718e3744 471
d62a17ae 472 /* No match. */
473 return 0;
718e3744 474}
475
d62a17ae 476static char *lcommunity_str_get(struct lcommunity *lcom, int i)
57d187bc 477{
d62a17ae 478 struct lcommunity_val lcomval;
d7c0a89a
QY
479 uint32_t globaladmin;
480 uint32_t localdata1;
481 uint32_t localdata2;
d62a17ae 482 char *str;
d7c0a89a 483 uint8_t *ptr;
d62a17ae 484 char *pnt;
485
ff9104a2 486 ptr = lcom->val + (i * LCOMMUNITY_SIZE);
d62a17ae 487
488 memcpy(&lcomval, ptr, LCOMMUNITY_SIZE);
489
490 /* Allocate memory. 48 bytes taken off bgp_lcommunity.c */
491 str = pnt = XMALLOC(MTYPE_LCOMMUNITY_STR, 48);
492
d7c0a89a 493 ptr = (uint8_t *)lcomval.val;
937652c6
DL
494 ptr = ptr_get_be32(ptr, &globaladmin);
495 ptr = ptr_get_be32(ptr, &localdata1);
496 ptr = ptr_get_be32(ptr, &localdata2);
497 (void)ptr; /* consume value */
d62a17ae 498
499 sprintf(pnt, "%u:%u:%u", globaladmin, localdata1, localdata2);
500 pnt += strlen(pnt);
501 *pnt = '\0';
502
503 return str;
57d187bc
JS
504}
505
506/* Internal function to perform regular expression match for
2acb4ac2 507 * a single community. */
d62a17ae 508static int lcommunity_regexp_include(regex_t *reg, struct lcommunity *lcom,
509 int i)
57d187bc 510{
d62a17ae 511 char *str;
512
513 /* When there is no communities attribute it is treated as empty string.
514 */
515 if (lcom == NULL || lcom->size == 0)
516 str = XSTRDUP(MTYPE_LCOMMUNITY_STR, "");
517 else
518 str = lcommunity_str_get(lcom, i);
519
520 /* Regular expression match. */
521 if (regexec(reg, str, 0, NULL, 0) == 0) {
522 XFREE(MTYPE_LCOMMUNITY_STR, str);
523 return 1;
524 }
57d187bc 525
d62a17ae 526 XFREE(MTYPE_LCOMMUNITY_STR, str);
527 /* No match. */
528 return 0;
57d187bc
JS
529}
530
d62a17ae 531static int lcommunity_regexp_match(struct lcommunity *com, regex_t *reg)
57d187bc 532{
d62a17ae 533 const char *str;
57d187bc 534
d62a17ae 535 /* When there is no communities attribute it is treated as empty
536 string. */
537 if (com == NULL || com->size == 0)
538 str = "";
539 else
8d9b8ed9 540 str = lcommunity_str(com, false);
57d187bc 541
d62a17ae 542 /* Regular expression match. */
543 if (regexec(reg, str, 0, NULL, 0) == 0)
544 return 1;
57d187bc 545
d62a17ae 546 /* No match. */
547 return 0;
57d187bc
JS
548}
549
550
d62a17ae 551static int ecommunity_regexp_match(struct ecommunity *ecom, regex_t *reg)
8708b74f 552{
d62a17ae 553 const char *str;
8708b74f 554
d62a17ae 555 /* When there is no communities attribute it is treated as empty
556 string. */
557 if (ecom == NULL || ecom->size == 0)
558 str = "";
559 else
560 str = ecommunity_str(ecom);
8708b74f 561
d62a17ae 562 /* Regular expression match. */
563 if (regexec(reg, str, 0, NULL, 0) == 0)
564 return 1;
8708b74f 565
d62a17ae 566 /* No match. */
567 return 0;
8708b74f 568}
569
ffd0c037 570#if 0
718e3744 571/* Delete community attribute using regular expression match. Return
572 modified communites attribute. */
573static struct community *
8708b74f 574community_regexp_delete (struct community *com, regex_t * reg)
718e3744 575{
aa861c10
C
576 int i;
577 uint32_t comval;
578 /* Maximum is "65535:65535" + '\0'. */
579 char c[12];
580 const char *str;
581
582 if (!com)
583 return NULL;
584
585 i = 0;
586 while (i < com->size)
587 {
588 memcpy (&comval, com_nthval (com, i), sizeof (uint32_t));
589 comval = ntohl (comval);
590
591 switch (comval) {
592 case COMMUNITY_INTERNET:
593 str = "internet";
594 break;
595 case COMMUNITY_ACCEPT_OWN:
596 str = "accept-own";
597 break;
598 case COMMUNITY_ROUTE_FILTER_TRANSLATED_v4:
599 str = "route-filter-translated-v4";
600 break;
601 case COMMUNITY_ROUTE_FILTER_v4:
602 str = "route-filter-v4";
603 break;
604 case COMMUNITY_ROUTE_FILTER_TRANSLATED_v6:
605 str = "route-filter-translated-v6";
606 break;
607 case COMMUNITY_ROUTE_FILTER_v6:
608 str = "route-filter-v6";
609 break;
610 case COMMUNITY_LLGR_STALE:
611 str = "llgr-stale";
612 break;
613 case COMMUNITY_NO_LLGR:
614 str = "no-llgr";
615 break;
616 case COMMUNITY_ACCEPT_OWN_NEXTHOP:
617 str = "accept-own-nexthop";
618 break;
619 case COMMUNITY_BLACKHOLE:
620 str = "blackhole";
621 break;
622 case COMMUNITY_NO_EXPORT:
623 str = "no-export";
624 break;
625 case COMMUNITY_NO_ADVERTISE:
626 str = "no-advertise";
627 break;
628 case COMMUNITY_LOCAL_AS:
629 str = "local-AS";
630 break;
631 case COMMUNITY_NO_PEER:
632 str = "no-peer";
633 break;
634 default:
635 sprintf (c, "%d:%d", (comval >> 16) & 0xFFFF,
636 comval & 0xFFFF);
637 str = c;
638 break;
639 }
640
641 if (regexec (reg, str, 0, NULL, 0) == 0)
642 community_del_val (com, com_nthval (com, i));
643 else
644 i++;
645 }
646 return com;
718e3744 647}
ffd0c037 648#endif
718e3744 649
650/* When given community attribute matches to the community-list return
651 1 else return 0. */
d62a17ae 652int community_list_match(struct community *com, struct community_list *list)
718e3744 653{
d62a17ae 654 struct community_entry *entry;
655
656 for (entry = list->head; entry; entry = entry->next) {
657 if (entry->any)
658 return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
659
660 if (entry->style == COMMUNITY_LIST_STANDARD) {
661 if (community_include(entry->u.com, COMMUNITY_INTERNET))
662 return entry->direct == COMMUNITY_PERMIT ? 1
663 : 0;
664
665 if (community_match(com, entry->u.com))
666 return entry->direct == COMMUNITY_PERMIT ? 1
667 : 0;
668 } else if (entry->style == COMMUNITY_LIST_EXPANDED) {
669 if (community_regexp_match(com, entry->reg))
670 return entry->direct == COMMUNITY_PERMIT ? 1
671 : 0;
672 }
673 }
674 return 0;
8708b74f 675}
676
d62a17ae 677int lcommunity_list_match(struct lcommunity *lcom, struct community_list *list)
57d187bc 678{
d62a17ae 679 struct community_entry *entry;
680
681 for (entry = list->head; entry; entry = entry->next) {
682 if (entry->any)
683 return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
684
685 if (entry->style == LARGE_COMMUNITY_LIST_STANDARD) {
686 if (lcommunity_match(lcom, entry->u.lcom))
687 return entry->direct == COMMUNITY_PERMIT ? 1
688 : 0;
689 } else if (entry->style == LARGE_COMMUNITY_LIST_EXPANDED) {
690 if (lcommunity_regexp_match(lcom, entry->reg))
691 return entry->direct == COMMUNITY_PERMIT ? 1
692 : 0;
693 }
694 }
695 return 0;
57d187bc
JS
696}
697
d62a17ae 698int ecommunity_list_match(struct ecommunity *ecom, struct community_list *list)
8708b74f 699{
d62a17ae 700 struct community_entry *entry;
701
702 for (entry = list->head; entry; entry = entry->next) {
703 if (entry->any)
704 return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
705
706 if (entry->style == EXTCOMMUNITY_LIST_STANDARD) {
707 if (ecommunity_match(ecom, entry->u.ecom))
708 return entry->direct == COMMUNITY_PERMIT ? 1
709 : 0;
710 } else if (entry->style == EXTCOMMUNITY_LIST_EXPANDED) {
711 if (ecommunity_regexp_match(ecom, entry->reg))
712 return entry->direct == COMMUNITY_PERMIT ? 1
713 : 0;
714 }
715 }
716 return 0;
718e3744 717}
718
719/* Perform exact matching. In case of expanded community-list, do
720 same thing as community_list_match(). */
d62a17ae 721int community_list_exact_match(struct community *com,
722 struct community_list *list)
718e3744 723{
d62a17ae 724 struct community_entry *entry;
725
726 for (entry = list->head; entry; entry = entry->next) {
727 if (entry->any)
728 return entry->direct == COMMUNITY_PERMIT ? 1 : 0;
729
730 if (entry->style == COMMUNITY_LIST_STANDARD) {
731 if (community_include(entry->u.com, COMMUNITY_INTERNET))
732 return entry->direct == COMMUNITY_PERMIT ? 1
733 : 0;
734
735 if (community_cmp(com, entry->u.com))
736 return entry->direct == COMMUNITY_PERMIT ? 1
737 : 0;
738 } else if (entry->style == COMMUNITY_LIST_EXPANDED) {
739 if (community_regexp_match(com, entry->reg))
740 return entry->direct == COMMUNITY_PERMIT ? 1
741 : 0;
742 }
743 }
744 return 0;
718e3744 745}
746
8708b74f 747/* Delete all permitted communities in the list from com. */
d62a17ae 748struct community *community_list_match_delete(struct community *com,
749 struct community_list *list)
718e3744 750{
d62a17ae 751 struct community_entry *entry;
d7c0a89a
QY
752 uint32_t val;
753 uint32_t com_index_to_delete[com->size];
d62a17ae 754 int delete_index = 0;
755 int i;
756
757 /* Loop over each community value and evaluate each against the
758 * community-list. If we need to delete a community value add its index
ceead39c 759 * to com_index_to_delete.
d62a17ae 760 */
761 for (i = 0; i < com->size; i++) {
762 val = community_val_get(com, i);
763
764 for (entry = list->head; entry; entry = entry->next) {
765 if (entry->any) {
766 if (entry->direct == COMMUNITY_PERMIT) {
767 com_index_to_delete[delete_index] = i;
768 delete_index++;
769 }
770 break;
771 }
772
773 else if ((entry->style == COMMUNITY_LIST_STANDARD)
774 && (community_include(entry->u.com,
775 COMMUNITY_INTERNET)
776 || community_include(entry->u.com, val))) {
777 if (entry->direct == COMMUNITY_PERMIT) {
778 com_index_to_delete[delete_index] = i;
779 delete_index++;
780 }
781 break;
782 }
783
784 else if ((entry->style == COMMUNITY_LIST_EXPANDED)
785 && community_regexp_include(entry->reg, com,
786 i)) {
787 if (entry->direct == COMMUNITY_PERMIT) {
788 com_index_to_delete[delete_index] = i;
789 delete_index++;
790 }
791 break;
792 }
793 }
794 }
5cbea288 795
d62a17ae 796 /* Delete all of the communities we flagged for deletion */
797 for (i = delete_index - 1; i >= 0; i--) {
798 val = community_val_get(com, com_index_to_delete[i]);
799 community_del_val(com, &val);
800 }
5cbea288 801
d62a17ae 802 return com;
718e3744 803}
804
805/* To avoid duplicated entry in the community-list, this function
806 compares specified entry to existing entry. */
d62a17ae 807static int community_list_dup_check(struct community_list *list,
808 struct community_entry *new)
718e3744 809{
d62a17ae 810 struct community_entry *entry;
811
812 for (entry = list->head; entry; entry = entry->next) {
813 if (entry->style != new->style)
814 continue;
815
816 if (entry->direct != new->direct)
817 continue;
818
819 if (entry->any != new->any)
820 continue;
821
822 if (entry->any)
823 return 1;
824
825 switch (entry->style) {
826 case COMMUNITY_LIST_STANDARD:
827 if (community_cmp(entry->u.com, new->u.com))
828 return 1;
829 break;
830 case LARGE_COMMUNITY_LIST_STANDARD:
831 if (lcommunity_cmp(entry->u.lcom, new->u.lcom))
832 return 1;
833 break;
834 case EXTCOMMUNITY_LIST_STANDARD:
835 if (ecommunity_cmp(entry->u.ecom, new->u.ecom))
836 return 1;
837 break;
838 case COMMUNITY_LIST_EXPANDED:
839 case EXTCOMMUNITY_LIST_EXPANDED:
840 case LARGE_COMMUNITY_LIST_EXPANDED:
841 if (strcmp(entry->config, new->config) == 0)
842 return 1;
843 break;
844 default:
845 break;
846 }
847 }
848 return 0;
718e3744 849}
6b0655a2 850
718e3744 851/* Set community-list. */
d62a17ae 852int community_list_set(struct community_list_handler *ch, const char *name,
853 const char *str, int direct, int style)
718e3744 854{
d62a17ae 855 struct community_entry *entry = NULL;
856 struct community_list *list;
857 struct community *com = NULL;
858 regex_t *regex = NULL;
859
860 /* Get community list. */
861 list = community_list_get(ch, name, COMMUNITY_LIST_MASTER);
862
863 /* When community-list already has entry, new entry should have same
864 style. If you want to have mixed style community-list, you can
865 comment out this check. */
866 if (!community_list_empty_p(list)) {
867 struct community_entry *first;
868
869 first = list->head;
870
871 if (style != first->style) {
872 return (first->style == COMMUNITY_LIST_STANDARD
873 ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT
874 : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT);
875 }
fee6e4e4 876 }
718e3744 877
d62a17ae 878 if (str) {
879 if (style == COMMUNITY_LIST_STANDARD)
880 com = community_str2com(str);
881 else
882 regex = bgp_regcomp(str);
8708b74f 883
d62a17ae 884 if (!com && !regex)
885 return COMMUNITY_LIST_ERR_MALFORMED_VAL;
886 }
718e3744 887
d62a17ae 888 entry = community_entry_new();
889 entry->direct = direct;
890 entry->style = style;
891 entry->any = (str ? 0 : 1);
892 entry->u.com = com;
893 entry->reg = regex;
894 entry->config =
895 (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL);
896
897 /* Do not put duplicated community entry. */
898 if (community_list_dup_check(list, entry))
899 community_entry_free(entry);
900 else {
901 community_list_entry_add(list, entry);
902 route_map_notify_dependencies(name, RMAP_EVENT_CLIST_ADDED);
903 }
718e3744 904
d62a17ae 905 return 0;
718e3744 906}
907
813d4307 908/* Unset community-list */
d62a17ae 909int community_list_unset(struct community_list_handler *ch, const char *name,
7298a8e1 910 const char *str, int direct, int style)
718e3744 911{
3571a6a2 912 struct community_list_master *cm = NULL;
d62a17ae 913 struct community_entry *entry = NULL;
914 struct community_list *list;
915 struct community *com = NULL;
916
917 /* Lookup community list. */
e237b0d2 918 list = community_list_lookup(ch, name, 0, COMMUNITY_LIST_MASTER);
d62a17ae 919 if (list == NULL)
920 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
921
3571a6a2 922 cm = community_list_master_lookup(ch, COMMUNITY_LIST_MASTER);
d62a17ae 923 /* Delete all of entry belongs to this community-list. */
7298a8e1 924 if (!str) {
3571a6a2 925 community_list_delete(cm, list);
d62a17ae 926 route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED);
927 return 0;
928 }
718e3744 929
7298a8e1
QY
930 if (style == COMMUNITY_LIST_STANDARD)
931 com = community_str2com(str);
718e3744 932
d62a17ae 933 if (com) {
934 entry = community_list_entry_lookup(list, com, direct);
3c1f53de 935 community_free(&com);
d62a17ae 936 } else
937 entry = community_list_entry_lookup(list, str, direct);
fee6e4e4 938
d62a17ae 939 if (!entry)
940 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
718e3744 941
3571a6a2 942 community_list_entry_delete(cm, list, entry);
d62a17ae 943 route_map_notify_dependencies(name, RMAP_EVENT_CLIST_DELETED);
718e3744 944
d62a17ae 945 return 0;
718e3744 946}
947
57d187bc 948/* Delete all permitted large communities in the list from com. */
d62a17ae 949struct lcommunity *lcommunity_list_match_delete(struct lcommunity *lcom,
950 struct community_list *list)
57d187bc 951{
d62a17ae 952 struct community_entry *entry;
d7c0a89a
QY
953 uint32_t com_index_to_delete[lcom->size];
954 uint8_t *ptr;
d62a17ae 955 int delete_index = 0;
956 int i;
957
958 /* Loop over each lcommunity value and evaluate each against the
959 * community-list. If we need to delete a community value add its index
ceead39c 960 * to com_index_to_delete.
d62a17ae 961 */
d62a17ae 962 for (i = 0; i < lcom->size; i++) {
534fc195 963 ptr = lcom->val + (i * LCOMMUNITY_SIZE);
d62a17ae 964 for (entry = list->head; entry; entry = entry->next) {
965 if (entry->any) {
966 if (entry->direct == COMMUNITY_PERMIT) {
967 com_index_to_delete[delete_index] = i;
968 delete_index++;
969 }
970 break;
971 }
972
973 else if ((entry->style == LARGE_COMMUNITY_LIST_STANDARD)
974 && lcommunity_include(entry->u.lcom, ptr)) {
975 if (entry->direct == COMMUNITY_PERMIT) {
976 com_index_to_delete[delete_index] = i;
977 delete_index++;
978 }
979 break;
980 }
981
cde8d696 982 else if ((entry->style == LARGE_COMMUNITY_LIST_EXPANDED)
d62a17ae 983 && lcommunity_regexp_include(entry->reg, lcom,
984 i)) {
985 if (entry->direct == COMMUNITY_PERMIT) {
986 com_index_to_delete[delete_index] = i;
987 delete_index++;
988 }
989 break;
990 }
991 }
992 }
57d187bc 993
d62a17ae 994 /* Delete all of the communities we flagged for deletion */
d62a17ae 995 for (i = delete_index - 1; i >= 0; i--) {
0a7dce9b 996 ptr = lcom->val + (com_index_to_delete[i] * LCOMMUNITY_SIZE);
d62a17ae 997 lcommunity_del_val(lcom, ptr);
998 }
57d187bc 999
d62a17ae 1000 return lcom;
57d187bc
JS
1001}
1002
1003/* Set lcommunity-list. */
d62a17ae 1004int lcommunity_list_set(struct community_list_handler *ch, const char *name,
1005 const char *str, int direct, int style)
57d187bc 1006{
d62a17ae 1007 struct community_entry *entry = NULL;
1008 struct community_list *list;
1009 struct lcommunity *lcom = NULL;
1010 regex_t *regex = NULL;
1011
1012 /* Get community list. */
1013 list = community_list_get(ch, name, LARGE_COMMUNITY_LIST_MASTER);
1014
1015 /* When community-list already has entry, new entry should have same
1016 style. If you want to have mixed style community-list, you can
1017 comment out this check. */
1018 if (!community_list_empty_p(list)) {
1019 struct community_entry *first;
1020
1021 first = list->head;
1022
1023 if (style != first->style) {
1024 return (first->style == COMMUNITY_LIST_STANDARD
1025 ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT
1026 : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT);
1027 }
1028 }
57d187bc 1029
d62a17ae 1030 if (str) {
1031 if (style == LARGE_COMMUNITY_LIST_STANDARD)
1032 lcom = lcommunity_str2com(str);
1033 else
1034 regex = bgp_regcomp(str);
57d187bc 1035
d62a17ae 1036 if (!lcom && !regex)
1037 return COMMUNITY_LIST_ERR_MALFORMED_VAL;
1038 }
57d187bc 1039
d62a17ae 1040 entry = community_entry_new();
1041 entry->direct = direct;
1042 entry->style = style;
1043 entry->any = (str ? 0 : 1);
1044 entry->u.lcom = lcom;
1045 entry->reg = regex;
8d9b8ed9
PM
1046 entry->config =
1047 (regex ? XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str) : NULL);
d62a17ae 1048
1049 /* Do not put duplicated community entry. */
1050 if (community_list_dup_check(list, entry))
1051 community_entry_free(entry);
35f6f850 1052 else {
d62a17ae 1053 community_list_entry_add(list, entry);
35f6f850 1054 route_map_notify_dependencies(name, RMAP_EVENT_LLIST_ADDED);
1055 }
d62a17ae 1056
1057 return 0;
57d187bc
JS
1058}
1059
1060/* Unset community-list. When str is NULL, delete all of
1061 community-list entry belongs to the specified name. */
d62a17ae 1062int lcommunity_list_unset(struct community_list_handler *ch, const char *name,
1063 const char *str, int direct, int style)
57d187bc 1064{
3571a6a2 1065 struct community_list_master *cm = NULL;
d62a17ae 1066 struct community_entry *entry = NULL;
1067 struct community_list *list;
1068 struct lcommunity *lcom = NULL;
1069 regex_t *regex = NULL;
1070
1071 /* Lookup community list. */
e237b0d2 1072 list = community_list_lookup(ch, name, 0, LARGE_COMMUNITY_LIST_MASTER);
d62a17ae 1073 if (list == NULL)
1074 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
1075
3571a6a2 1076 cm = community_list_master_lookup(ch, LARGE_COMMUNITY_LIST_MASTER);
d62a17ae 1077 /* Delete all of entry belongs to this community-list. */
1078 if (!str) {
3571a6a2 1079 community_list_delete(cm, list);
35f6f850 1080 route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED);
d62a17ae 1081 return 0;
1082 }
57d187bc 1083
d62a17ae 1084 if (style == LARGE_COMMUNITY_LIST_STANDARD)
1085 lcom = lcommunity_str2com(str);
1086 else
1087 regex = bgp_regcomp(str);
57d187bc 1088
d62a17ae 1089 if (!lcom && !regex)
1090 return COMMUNITY_LIST_ERR_MALFORMED_VAL;
57d187bc 1091
d62a17ae 1092 if (lcom)
1093 entry = community_list_entry_lookup(list, lcom, direct);
1094 else
1095 entry = community_list_entry_lookup(list, str, direct);
57d187bc 1096
d62a17ae 1097 if (lcom)
1098 lcommunity_free(&lcom);
1099 if (regex)
1100 bgp_regex_free(regex);
57d187bc 1101
d62a17ae 1102 if (!entry)
1103 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
57d187bc 1104
3571a6a2 1105 community_list_entry_delete(cm, list, entry);
35f6f850 1106 route_map_notify_dependencies(name, RMAP_EVENT_LLIST_DELETED);
57d187bc 1107
d62a17ae 1108 return 0;
57d187bc
JS
1109}
1110
718e3744 1111/* Set extcommunity-list. */
d62a17ae 1112int extcommunity_list_set(struct community_list_handler *ch, const char *name,
1113 const char *str, int direct, int style)
718e3744 1114{
d62a17ae 1115 struct community_entry *entry = NULL;
1116 struct community_list *list;
1117 struct ecommunity *ecom = NULL;
1118 regex_t *regex = NULL;
718e3744 1119
fa301630 1120 if (str == NULL)
1121 return COMMUNITY_LIST_ERR_MALFORMED_VAL;
1122
d62a17ae 1123 /* Get community list. */
1124 list = community_list_get(ch, name, EXTCOMMUNITY_LIST_MASTER);
718e3744 1125
d62a17ae 1126 /* When community-list already has entry, new entry should have same
1127 style. If you want to have mixed style community-list, you can
1128 comment out this check. */
1129 if (!community_list_empty_p(list)) {
1130 struct community_entry *first;
718e3744 1131
d62a17ae 1132 first = list->head;
718e3744 1133
d62a17ae 1134 if (style != first->style) {
1135 return (first->style == EXTCOMMUNITY_LIST_STANDARD
1136 ? COMMUNITY_LIST_ERR_STANDARD_CONFLICT
1137 : COMMUNITY_LIST_ERR_EXPANDED_CONFLICT);
1138 }
fee6e4e4 1139 }
718e3744 1140
d62a17ae 1141 if (style == EXTCOMMUNITY_LIST_STANDARD)
1142 ecom = ecommunity_str2com(str, 0, 1);
1143 else
1144 regex = bgp_regcomp(str);
1145
1146 if (!ecom && !regex)
1147 return COMMUNITY_LIST_ERR_MALFORMED_VAL;
1148
1149 if (ecom)
1150 ecom->str =
1151 ecommunity_ecom2str(ecom, ECOMMUNITY_FORMAT_DISPLAY, 0);
1152
1153 entry = community_entry_new();
1154 entry->direct = direct;
1155 entry->style = style;
fa301630 1156 entry->any = 0;
d62a17ae 1157 if (ecom)
1158 entry->config = ecommunity_ecom2str(
1159 ecom, ECOMMUNITY_FORMAT_COMMUNITY_LIST, 0);
1160 else if (regex)
1161 entry->config = XSTRDUP(MTYPE_COMMUNITY_LIST_CONFIG, str);
1162
1163 entry->u.ecom = ecom;
1164 entry->reg = regex;
1165
1166 /* Do not put duplicated community entry. */
1167 if (community_list_dup_check(list, entry))
1168 community_entry_free(entry);
1169 else {
1170 community_list_entry_add(list, entry);
1171 route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_ADDED);
1172 }
718e3744 1173
d62a17ae 1174 return 0;
718e3744 1175}
1176
7298a8e1
QY
1177/* Unset extcommunity-list.
1178 *
1179 * When str is NULL, delete all extcommunity-list entries belonging to the
1180 * specified name.
1181 */
d62a17ae 1182int extcommunity_list_unset(struct community_list_handler *ch, const char *name,
7298a8e1 1183 const char *str, int direct, int style)
718e3744 1184{
3571a6a2 1185 struct community_list_master *cm = NULL;
d62a17ae 1186 struct community_entry *entry = NULL;
1187 struct community_list *list;
1188 struct ecommunity *ecom = NULL;
1189
1190 /* Lookup extcommunity list. */
e237b0d2 1191 list = community_list_lookup(ch, name, 0, EXTCOMMUNITY_LIST_MASTER);
d62a17ae 1192 if (list == NULL)
1193 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
1194
3571a6a2 1195 cm = community_list_master_lookup(ch, EXTCOMMUNITY_LIST_MASTER);
d62a17ae 1196 /* Delete all of entry belongs to this extcommunity-list. */
7298a8e1 1197 if (!str) {
3571a6a2 1198 community_list_delete(cm, list);
d62a17ae 1199 route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED);
1200 return 0;
1201 }
718e3744 1202
7298a8e1
QY
1203 if (style == EXTCOMMUNITY_LIST_STANDARD)
1204 ecom = ecommunity_str2com(str, 0, 1);
718e3744 1205
d62a17ae 1206 if (ecom) {
1207 entry = community_list_entry_lookup(list, ecom, direct);
1208 ecommunity_free(&ecom);
1209 } else
1210 entry = community_list_entry_lookup(list, str, direct);
fee6e4e4 1211
d62a17ae 1212 if (!entry)
1213 return COMMUNITY_LIST_ERR_CANT_FIND_LIST;
718e3744 1214
3571a6a2 1215 community_list_entry_delete(cm, list, entry);
d62a17ae 1216 route_map_notify_dependencies(name, RMAP_EVENT_ECLIST_DELETED);
718e3744 1217
d62a17ae 1218 return 0;
718e3744 1219}
1220
1221/* Initializa community-list. Return community-list handler. */
d62a17ae 1222struct community_list_handler *community_list_init(void)
718e3744 1223{
d62a17ae 1224 struct community_list_handler *ch;
1225 ch = XCALLOC(MTYPE_COMMUNITY_LIST_HANDLER,
1226 sizeof(struct community_list_handler));
3571a6a2
DS
1227
1228 ch->community_list.hash =
1229 hash_create_size(4, bgp_clist_hash_key_community_list,
1230 bgp_clist_hash_cmp_community_list,
1231 "Community List Number Quick Lookup");
1232
1233 ch->extcommunity_list.hash =
1234 hash_create_size(4, bgp_clist_hash_key_community_list,
1235 bgp_clist_hash_cmp_community_list,
1236 "Extended Community List Quick Lookup");
1237
1238 ch->lcommunity_list.hash =
1239 hash_create_size(4, bgp_clist_hash_key_community_list,
1240 bgp_clist_hash_cmp_community_list,
1241 "Large Community List Quick Lookup");
1242
d62a17ae 1243 return ch;
718e3744 1244}
1245
1246/* Terminate community-list. */
d62a17ae 1247void community_list_terminate(struct community_list_handler *ch)
718e3744 1248{
d62a17ae 1249 struct community_list_master *cm;
1250 struct community_list *list;
1251
1252 cm = &ch->community_list;
1253 while ((list = cm->num.head) != NULL)
3571a6a2 1254 community_list_delete(cm, list);
d62a17ae 1255 while ((list = cm->str.head) != NULL)
3571a6a2
DS
1256 community_list_delete(cm, list);
1257 hash_free(cm->hash);
d62a17ae 1258
1259 cm = &ch->lcommunity_list;
1260 while ((list = cm->num.head) != NULL)
3571a6a2 1261 community_list_delete(cm, list);
d62a17ae 1262 while ((list = cm->str.head) != NULL)
3571a6a2
DS
1263 community_list_delete(cm, list);
1264 hash_free(cm->hash);
d62a17ae 1265
1266 cm = &ch->extcommunity_list;
1267 while ((list = cm->num.head) != NULL)
3571a6a2 1268 community_list_delete(cm, list);
d62a17ae 1269 while ((list = cm->str.head) != NULL)
3571a6a2
DS
1270 community_list_delete(cm, list);
1271 hash_free(cm->hash);
d62a17ae 1272
1273 XFREE(MTYPE_COMMUNITY_LIST_HANDLER, ch);
718e3744 1274}