]> git.proxmox.com Git - mirror_frr.git/blob - bgpd/bgp_filter.c
Merge pull request #8250 from idryzhov/fix-nb-running-get-entry
[mirror_frr.git] / bgpd / bgp_filter.c
1 /* AS path filter list.
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 */
20
21 #include <zebra.h>
22
23 #include "command.h"
24 #include "log.h"
25 #include "memory.h"
26 #include "buffer.h"
27 #include "queue.h"
28 #include "filter.h"
29
30 #include "bgpd/bgpd.h"
31 #include "bgpd/bgp_aspath.h"
32 #include "bgpd/bgp_regex.h"
33 #include "bgpd/bgp_filter.h"
34
35 /* List of AS filter list. */
36 struct as_list_list {
37 struct as_list *head;
38 struct as_list *tail;
39 };
40
41 /* AS path filter master. */
42 struct as_list_master {
43 /* List of access_list which name is number. */
44 struct as_list_list num;
45
46 /* List of access_list which name is string. */
47 struct as_list_list str;
48
49 /* Hook function which is executed when new access_list is added. */
50 void (*add_hook)(char *);
51
52 /* Hook function which is executed when access_list is deleted. */
53 void (*delete_hook)(const char *);
54 };
55
56 /* Element of AS path filter. */
57 struct as_filter {
58 struct as_filter *next;
59 struct as_filter *prev;
60
61 enum as_filter_type type;
62
63 regex_t *reg;
64 char *reg_str;
65
66 /* Sequence number. */
67 int64_t seq;
68 };
69
70 /* AS path filter list. */
71 struct as_list {
72 char *name;
73
74 enum access_type type;
75
76 struct as_list *next;
77 struct as_list *prev;
78
79 struct as_filter *head;
80 struct as_filter *tail;
81 };
82
83
84 /* Calculate new sequential number. */
85 static int64_t bgp_alist_new_seq_get(struct as_list *list)
86 {
87 int64_t maxseq;
88 int64_t newseq;
89 struct as_filter *entry;
90
91 maxseq = 0;
92
93 for (entry = list->head; entry; entry = entry->next) {
94 if (maxseq < entry->seq)
95 maxseq = entry->seq;
96 }
97
98 newseq = ((maxseq / 5) * 5) + 5;
99
100 return (newseq > UINT_MAX) ? UINT_MAX : newseq;
101 }
102
103 /* Return as-list entry which has same seq number. */
104 static struct as_filter *bgp_aslist_seq_check(struct as_list *list, int64_t seq)
105 {
106 struct as_filter *entry;
107
108 for (entry = list->head; entry; entry = entry->next)
109 if (entry->seq == seq)
110 return entry;
111
112 return NULL;
113 }
114
115 /* as-path access-list 10 permit AS1. */
116
117 static struct as_list_master as_list_master = {{NULL, NULL},
118 {NULL, NULL},
119 NULL,
120 NULL};
121
122 /* Allocate new AS filter. */
123 static struct as_filter *as_filter_new(void)
124 {
125 return XCALLOC(MTYPE_AS_FILTER, sizeof(struct as_filter));
126 }
127
128 /* Free allocated AS filter. */
129 static void as_filter_free(struct as_filter *asfilter)
130 {
131 if (asfilter->reg)
132 bgp_regex_free(asfilter->reg);
133 XFREE(MTYPE_AS_FILTER_STR, asfilter->reg_str);
134 XFREE(MTYPE_AS_FILTER, asfilter);
135 }
136
137 /* Make new AS filter. */
138 static struct as_filter *as_filter_make(regex_t *reg, const char *reg_str,
139 enum as_filter_type type)
140 {
141 struct as_filter *asfilter;
142
143 asfilter = as_filter_new();
144 asfilter->reg = reg;
145 asfilter->type = type;
146 asfilter->reg_str = XSTRDUP(MTYPE_AS_FILTER_STR, reg_str);
147
148 return asfilter;
149 }
150
151 static struct as_filter *as_filter_lookup(struct as_list *aslist,
152 const char *reg_str,
153 enum as_filter_type type)
154 {
155 struct as_filter *asfilter;
156
157 for (asfilter = aslist->head; asfilter; asfilter = asfilter->next)
158 if (strcmp(reg_str, asfilter->reg_str) == 0)
159 return asfilter;
160 return NULL;
161 }
162
163 static void as_filter_entry_replace(struct as_list *list,
164 struct as_filter *replace,
165 struct as_filter *entry)
166 {
167 if (replace->next) {
168 entry->next = replace->next;
169 replace->next->prev = entry;
170 } else {
171 entry->next = NULL;
172 list->tail = entry;
173 }
174
175 if (replace->prev) {
176 entry->prev = replace->prev;
177 replace->prev->next = entry;
178 } else {
179 entry->prev = NULL;
180 list->head = entry;
181 }
182
183 as_filter_free(replace);
184 }
185
186 static void as_list_filter_add(struct as_list *aslist,
187 struct as_filter *asfilter)
188 {
189 struct as_filter *point;
190 struct as_filter *replace;
191
192 if (aslist->tail && asfilter->seq > aslist->tail->seq)
193 point = NULL;
194 else {
195 replace = bgp_aslist_seq_check(aslist, asfilter->seq);
196 if (replace) {
197 as_filter_entry_replace(aslist, replace, asfilter);
198 return;
199 }
200
201 /* Check insert point. */
202 for (point = aslist->head; point; point = point->next)
203 if (point->seq >= asfilter->seq)
204 break;
205 }
206
207 asfilter->next = point;
208
209 if (point) {
210 if (point->prev)
211 point->prev->next = asfilter;
212 else
213 aslist->head = asfilter;
214
215 asfilter->prev = point->prev;
216 point->prev = asfilter;
217 } else {
218 if (aslist->tail)
219 aslist->tail->next = asfilter;
220 else
221 aslist->head = asfilter;
222
223 asfilter->prev = aslist->tail;
224 aslist->tail = asfilter;
225 }
226
227 /* Run hook function. */
228 if (as_list_master.add_hook)
229 (*as_list_master.add_hook)(aslist->name);
230 }
231
232 /* Lookup as_list from list of as_list by name. */
233 struct as_list *as_list_lookup(const char *name)
234 {
235 struct as_list *aslist;
236
237 if (name == NULL)
238 return NULL;
239
240 for (aslist = as_list_master.num.head; aslist; aslist = aslist->next)
241 if (strcmp(aslist->name, name) == 0)
242 return aslist;
243
244 for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
245 if (strcmp(aslist->name, name) == 0)
246 return aslist;
247
248 return NULL;
249 }
250
251 static struct as_list *as_list_new(void)
252 {
253 return XCALLOC(MTYPE_AS_LIST, sizeof(struct as_list));
254 }
255
256 static void as_list_free(struct as_list *aslist)
257 {
258 XFREE(MTYPE_AS_STR, aslist->name);
259 XFREE(MTYPE_AS_LIST, aslist);
260 }
261
262 /* Insert new AS list to list of as_list. Each as_list is sorted by
263 the name. */
264 static struct as_list *as_list_insert(const char *name)
265 {
266 size_t i;
267 long number;
268 struct as_list *aslist;
269 struct as_list *point;
270 struct as_list_list *list;
271
272 /* Allocate new access_list and copy given name. */
273 aslist = as_list_new();
274 aslist->name = XSTRDUP(MTYPE_AS_STR, name);
275 assert(aslist->name);
276
277 /* If name is made by all digit character. We treat it as
278 number. */
279 for (number = 0, i = 0; i < strlen(name); i++) {
280 if (isdigit((unsigned char)name[i]))
281 number = (number * 10) + (name[i] - '0');
282 else
283 break;
284 }
285
286 /* In case of name is all digit character */
287 if (i == strlen(name)) {
288 aslist->type = ACCESS_TYPE_NUMBER;
289
290 /* Set access_list to number list. */
291 list = &as_list_master.num;
292
293 for (point = list->head; point; point = point->next)
294 if (atol(point->name) >= number)
295 break;
296 } else {
297 aslist->type = ACCESS_TYPE_STRING;
298
299 /* Set access_list to string list. */
300 list = &as_list_master.str;
301
302 /* Set point to insertion point. */
303 for (point = list->head; point; point = point->next)
304 if (strcmp(point->name, name) >= 0)
305 break;
306 }
307
308 /* In case of this is the first element of master. */
309 if (list->head == NULL) {
310 list->head = list->tail = aslist;
311 return aslist;
312 }
313
314 /* In case of insertion is made at the tail of access_list. */
315 if (point == NULL) {
316 aslist->prev = list->tail;
317 list->tail->next = aslist;
318 list->tail = aslist;
319 return aslist;
320 }
321
322 /* In case of insertion is made at the head of access_list. */
323 if (point == list->head) {
324 aslist->next = list->head;
325 list->head->prev = aslist;
326 list->head = aslist;
327 return aslist;
328 }
329
330 /* Insertion is made at middle of the access_list. */
331 aslist->next = point;
332 aslist->prev = point->prev;
333
334 if (point->prev)
335 point->prev->next = aslist;
336 point->prev = aslist;
337
338 return aslist;
339 }
340
341 static struct as_list *as_list_get(const char *name)
342 {
343 struct as_list *aslist;
344
345 aslist = as_list_lookup(name);
346 if (aslist == NULL)
347 aslist = as_list_insert(name);
348
349 return aslist;
350 }
351
352 static const char *filter_type_str(enum as_filter_type type)
353 {
354 switch (type) {
355 case AS_FILTER_PERMIT:
356 return "permit";
357 case AS_FILTER_DENY:
358 return "deny";
359 default:
360 return "";
361 }
362 }
363
364 static void as_list_delete(struct as_list *aslist)
365 {
366 struct as_list_list *list;
367 struct as_filter *filter, *next;
368
369 for (filter = aslist->head; filter; filter = next) {
370 next = filter->next;
371 as_filter_free(filter);
372 }
373
374 if (aslist->type == ACCESS_TYPE_NUMBER)
375 list = &as_list_master.num;
376 else
377 list = &as_list_master.str;
378
379 if (aslist->next)
380 aslist->next->prev = aslist->prev;
381 else
382 list->tail = aslist->prev;
383
384 if (aslist->prev)
385 aslist->prev->next = aslist->next;
386 else
387 list->head = aslist->next;
388
389 as_list_free(aslist);
390 }
391
392 static bool as_list_empty(struct as_list *aslist)
393 {
394 return aslist->head == NULL && aslist->tail == NULL;
395 }
396
397 static void as_list_filter_delete(struct as_list *aslist,
398 struct as_filter *asfilter)
399 {
400 char *name = XSTRDUP(MTYPE_AS_STR, aslist->name);
401
402 if (asfilter->next)
403 asfilter->next->prev = asfilter->prev;
404 else
405 aslist->tail = asfilter->prev;
406
407 if (asfilter->prev)
408 asfilter->prev->next = asfilter->next;
409 else
410 aslist->head = asfilter->next;
411
412 as_filter_free(asfilter);
413
414 /* If access_list becomes empty delete it from access_master. */
415 if (as_list_empty(aslist))
416 as_list_delete(aslist);
417
418 /* Run hook function. */
419 if (as_list_master.delete_hook)
420 (*as_list_master.delete_hook)(name);
421 XFREE(MTYPE_AS_STR, name);
422 }
423
424 static bool as_filter_match(struct as_filter *asfilter, struct aspath *aspath)
425 {
426 return bgp_regexec(asfilter->reg, aspath) != REG_NOMATCH;
427 }
428
429 /* Apply AS path filter to AS. */
430 enum as_filter_type as_list_apply(struct as_list *aslist, void *object)
431 {
432 struct as_filter *asfilter;
433 struct aspath *aspath;
434
435 aspath = (struct aspath *)object;
436
437 if (aslist == NULL)
438 return AS_FILTER_DENY;
439
440 for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
441 if (as_filter_match(asfilter, aspath))
442 return asfilter->type;
443 }
444 return AS_FILTER_DENY;
445 }
446
447 /* Add hook function. */
448 void as_list_add_hook(void (*func)(char *))
449 {
450 as_list_master.add_hook = func;
451 }
452
453 /* Delete hook function. */
454 void as_list_delete_hook(void (*func)(const char *))
455 {
456 as_list_master.delete_hook = func;
457 }
458
459 static bool as_list_dup_check(struct as_list *aslist, struct as_filter *new)
460 {
461 struct as_filter *asfilter;
462
463 for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
464 if (asfilter->type == new->type
465 && strcmp(asfilter->reg_str, new->reg_str) == 0)
466 return true;
467 }
468 return false;
469 }
470
471 bool config_bgp_aspath_validate(const char *regstr)
472 {
473 char valid_chars[] = "1234567890_^|[,{}() ]$*+.?-\\";
474
475 if (strspn(regstr, valid_chars) == strlen(regstr))
476 return true;
477 return false;
478 }
479
480 DEFUN(as_path, bgp_as_path_cmd,
481 "bgp as-path access-list WORD [seq (0-4294967295)] <deny|permit> LINE...",
482 BGP_STR
483 "BGP autonomous system path filter\n"
484 "Specify an access list name\n"
485 "Regular expression access list name\n"
486 "Sequence number of an entry\n"
487 "Sequence number\n"
488 "Specify packets to reject\n"
489 "Specify packets to forward\n"
490 "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n")
491 {
492 int idx = 0;
493 enum as_filter_type type;
494 struct as_filter *asfilter;
495 struct as_list *aslist;
496 regex_t *regex;
497 char *regstr;
498 int64_t seqnum = ASPATH_SEQ_NUMBER_AUTO;
499
500 /* Retrieve access list name */
501 argv_find(argv, argc, "WORD", &idx);
502 char *alname = argv[idx]->arg;
503
504 if (argv_find(argv, argc, "(0-4294967295)", &idx))
505 seqnum = (int64_t)atol(argv[idx]->arg);
506
507 /* Check the filter type. */
508 type = argv_find(argv, argc, "deny", &idx) ? AS_FILTER_DENY
509 : AS_FILTER_PERMIT;
510
511 /* Check AS path regex. */
512 argv_find(argv, argc, "LINE", &idx);
513 regstr = argv_concat(argv, argc, idx);
514
515 regex = bgp_regcomp(regstr);
516 if (!regex) {
517 vty_out(vty, "can't compile regexp %s\n", regstr);
518 XFREE(MTYPE_TMP, regstr);
519 return CMD_WARNING_CONFIG_FAILED;
520 }
521
522 if (!config_bgp_aspath_validate(regstr)) {
523 vty_out(vty, "Invalid character in as-path access-list %s\n",
524 regstr);
525 return CMD_WARNING_CONFIG_FAILED;
526 }
527
528 asfilter = as_filter_make(regex, regstr, type);
529
530 XFREE(MTYPE_TMP, regstr);
531
532 /* Install new filter to the access_list. */
533 aslist = as_list_get(alname);
534
535 if (seqnum == ASPATH_SEQ_NUMBER_AUTO)
536 seqnum = bgp_alist_new_seq_get(aslist);
537
538 asfilter->seq = seqnum;
539
540 /* Duplicate insertion check. */;
541 if (as_list_dup_check(aslist, asfilter))
542 as_filter_free(asfilter);
543 else
544 as_list_filter_add(aslist, asfilter);
545
546 return CMD_SUCCESS;
547 }
548
549 DEFUN(no_as_path, no_bgp_as_path_cmd,
550 "no bgp as-path access-list WORD [seq (0-4294967295)] <deny|permit> LINE...",
551 NO_STR
552 BGP_STR
553 "BGP autonomous system path filter\n"
554 "Specify an access list name\n"
555 "Regular expression access list name\n"
556 "Sequence number of an entry\n"
557 "Sequence number\n"
558 "Specify packets to reject\n"
559 "Specify packets to forward\n"
560 "A regular-expression (1234567890_^|[,{}() ]$*+.?-\\) to match the BGP AS paths\n")
561 {
562 int idx = 0;
563 enum as_filter_type type;
564 struct as_filter *asfilter;
565 struct as_list *aslist;
566 char *regstr;
567 regex_t *regex;
568
569 char *aslistname =
570 argv_find(argv, argc, "WORD", &idx) ? argv[idx]->arg : NULL;
571
572 /* Lookup AS list from AS path list. */
573 aslist = as_list_lookup(aslistname);
574 if (aslist == NULL) {
575 vty_out(vty, "bgp as-path access-list %s doesn't exist\n",
576 aslistname);
577 return CMD_WARNING_CONFIG_FAILED;
578 }
579
580 /* Check the filter type. */
581 if (argv_find(argv, argc, "permit", &idx))
582 type = AS_FILTER_PERMIT;
583 else if (argv_find(argv, argc, "deny", &idx))
584 type = AS_FILTER_DENY;
585 else {
586 vty_out(vty, "filter type must be [permit|deny]\n");
587 return CMD_WARNING_CONFIG_FAILED;
588 }
589
590 /* Compile AS path. */
591 argv_find(argv, argc, "LINE", &idx);
592 regstr = argv_concat(argv, argc, idx);
593
594 if (!config_bgp_aspath_validate(regstr)) {
595 vty_out(vty, "Invalid character in as-path access-list %s\n",
596 regstr);
597 return CMD_WARNING_CONFIG_FAILED;
598 }
599
600 regex = bgp_regcomp(regstr);
601 if (!regex) {
602 vty_out(vty, "can't compile regexp %s\n", regstr);
603 XFREE(MTYPE_TMP, regstr);
604 return CMD_WARNING_CONFIG_FAILED;
605 }
606
607 /* Lookup asfilter. */
608 asfilter = as_filter_lookup(aslist, regstr, type);
609
610 bgp_regex_free(regex);
611
612 if (asfilter == NULL) {
613 vty_out(vty, "Regex entered %s does not exist\n", regstr);
614 XFREE(MTYPE_TMP, regstr);
615 return CMD_WARNING_CONFIG_FAILED;
616 }
617
618 XFREE(MTYPE_TMP, regstr);
619
620 as_list_filter_delete(aslist, asfilter);
621
622 return CMD_SUCCESS;
623 }
624
625 DEFUN (no_as_path_all,
626 no_bgp_as_path_all_cmd,
627 "no bgp as-path access-list WORD",
628 NO_STR
629 BGP_STR
630 "BGP autonomous system path filter\n"
631 "Specify an access list name\n"
632 "Regular expression access list name\n")
633 {
634 int idx_word = 4;
635 struct as_list *aslist;
636
637 aslist = as_list_lookup(argv[idx_word]->arg);
638 if (aslist == NULL) {
639 vty_out(vty, "bgp as-path access-list %s doesn't exist\n",
640 argv[idx_word]->arg);
641 return CMD_WARNING_CONFIG_FAILED;
642 }
643
644 as_list_delete(aslist);
645
646 /* Run hook function. */
647 if (as_list_master.delete_hook)
648 (*as_list_master.delete_hook)(argv[idx_word]->arg);
649
650 return CMD_SUCCESS;
651 }
652
653 static void as_list_show(struct vty *vty, struct as_list *aslist)
654 {
655 struct as_filter *asfilter;
656
657 vty_out(vty, "AS path access list %s\n", aslist->name);
658
659 for (asfilter = aslist->head; asfilter; asfilter = asfilter->next) {
660 vty_out(vty, " %s %s\n", filter_type_str(asfilter->type),
661 asfilter->reg_str);
662 }
663 }
664
665 static void as_list_show_all(struct vty *vty)
666 {
667 struct as_list *aslist;
668 struct as_filter *asfilter;
669
670 for (aslist = as_list_master.num.head; aslist; aslist = aslist->next) {
671 vty_out(vty, "AS path access list %s\n", aslist->name);
672
673 for (asfilter = aslist->head; asfilter;
674 asfilter = asfilter->next) {
675 vty_out(vty, " %s %s\n",
676 filter_type_str(asfilter->type),
677 asfilter->reg_str);
678 }
679 }
680
681 for (aslist = as_list_master.str.head; aslist; aslist = aslist->next) {
682 vty_out(vty, "AS path access list %s\n", aslist->name);
683
684 for (asfilter = aslist->head; asfilter;
685 asfilter = asfilter->next) {
686 vty_out(vty, " %s %s\n",
687 filter_type_str(asfilter->type),
688 asfilter->reg_str);
689 }
690 }
691 }
692
693 DEFUN (show_as_path_access_list,
694 show_bgp_as_path_access_list_cmd,
695 "show bgp as-path-access-list WORD",
696 SHOW_STR
697 BGP_STR
698 "List AS path access lists\n"
699 "AS path access list name\n")
700 {
701 int idx_word = 3;
702 struct as_list *aslist;
703
704 aslist = as_list_lookup(argv[idx_word]->arg);
705 if (aslist)
706 as_list_show(vty, aslist);
707
708 return CMD_SUCCESS;
709 }
710
711 ALIAS (show_as_path_access_list,
712 show_ip_as_path_access_list_cmd,
713 "show ip as-path-access-list WORD",
714 SHOW_STR
715 IP_STR
716 "List AS path access lists\n"
717 "AS path access list name\n")
718
719 DEFUN (show_as_path_access_list_all,
720 show_bgp_as_path_access_list_all_cmd,
721 "show bgp as-path-access-list",
722 SHOW_STR
723 BGP_STR
724 "List AS path access lists\n")
725 {
726 as_list_show_all(vty);
727 return CMD_SUCCESS;
728 }
729
730 ALIAS (show_as_path_access_list_all,
731 show_ip_as_path_access_list_all_cmd,
732 "show ip as-path-access-list",
733 SHOW_STR
734 IP_STR
735 "List AS path access lists\n")
736
737 static int config_write_as_list(struct vty *vty)
738 {
739 struct as_list *aslist;
740 struct as_filter *asfilter;
741 int write = 0;
742
743 for (aslist = as_list_master.num.head; aslist; aslist = aslist->next)
744 for (asfilter = aslist->head; asfilter;
745 asfilter = asfilter->next) {
746 vty_out(vty,
747 "bgp as-path access-list %s seq %" PRId64
748 " %s %s\n",
749 aslist->name, asfilter->seq,
750 filter_type_str(asfilter->type),
751 asfilter->reg_str);
752 write++;
753 }
754
755 for (aslist = as_list_master.str.head; aslist; aslist = aslist->next)
756 for (asfilter = aslist->head; asfilter;
757 asfilter = asfilter->next) {
758 vty_out(vty,
759 "bgp as-path access-list %s seq %" PRId64
760 " %s %s\n",
761 aslist->name, asfilter->seq,
762 filter_type_str(asfilter->type),
763 asfilter->reg_str);
764 write++;
765 }
766 return write;
767 }
768
769 static int config_write_as_list(struct vty *vty);
770 static struct cmd_node as_list_node = {
771 .name = "as list",
772 .node = AS_LIST_NODE,
773 .prompt = "",
774 .config_write = config_write_as_list,
775 };
776
777 /* Register functions. */
778 void bgp_filter_init(void)
779 {
780 install_node(&as_list_node);
781
782 install_element(CONFIG_NODE, &bgp_as_path_cmd);
783 install_element(CONFIG_NODE, &no_bgp_as_path_cmd);
784 install_element(CONFIG_NODE, &no_bgp_as_path_all_cmd);
785
786 install_element(VIEW_NODE, &show_bgp_as_path_access_list_cmd);
787 install_element(VIEW_NODE, &show_ip_as_path_access_list_cmd);
788 install_element(VIEW_NODE, &show_bgp_as_path_access_list_all_cmd);
789 install_element(VIEW_NODE, &show_ip_as_path_access_list_all_cmd);
790 }
791
792 void bgp_filter_reset(void)
793 {
794 struct as_list *aslist;
795 struct as_list *next;
796
797 for (aslist = as_list_master.num.head; aslist; aslist = next) {
798 next = aslist->next;
799 as_list_delete(aslist);
800 }
801
802 for (aslist = as_list_master.str.head; aslist; aslist = next) {
803 next = aslist->next;
804 as_list_delete(aslist);
805 }
806
807 assert(as_list_master.num.head == NULL);
808 assert(as_list_master.num.tail == NULL);
809
810 assert(as_list_master.str.head == NULL);
811 assert(as_list_master.str.tail == NULL);
812 }