File:  [ELWIX - Embedded LightWeight unIX -] / embedaddon / quagga / bgpd / bgp_filter.c
Revision 1.1.1.1 (vendor branch): download - view: text, annotated - select for diffs - revision graph
Tue Feb 21 17:26:12 2012 UTC (12 years, 4 months ago) by misho
Branches: quagga, MAIN
CVS tags: v0_99_22p0, v0_99_22, v0_99_21, v0_99_20_1, v0_99_20, HEAD
quagga

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

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>