Annotation of embedaddon/bird/proto/ospf/neighbor.c, revision 1.1.1.2
1.1 misho 1: /*
2: * BIRD -- OSPF
3: *
4: * (c) 1999--2004 Ondrej Filip <feela@network.cz>
5: * (c) 2009--2014 Ondrej Zajicek <santiago@crfreenet.org>
6: * (c) 2009--2014 CZ.NIC z.s.p.o.
7: *
8: * Can be freely distributed and used under the terms of the GNU GPL.
9: */
10:
11: #include "ospf.h"
12:
13:
14: const char *ospf_ns_names[] = {
15: "Down", "Attempt", "Init", "2-Way", "ExStart", "Exchange", "Loading", "Full"
16: };
17:
18: const char *ospf_inm_names[] = {
19: "HelloReceived", "Start", "2-WayReceived", "NegotiationDone", "ExchangeDone",
20: "BadLSReq", "LoadingDone", "AdjOK?", "SeqNumberMismatch", "1-WayReceived",
21: "KillNbr", "InactivityTimer", "LLDown"
22: };
23:
24:
25: static int can_do_adj(struct ospf_neighbor *n);
26: static void inactivity_timer_hook(timer * timer);
27: static void dbdes_timer_hook(timer *t);
28: static void lsrq_timer_hook(timer *t);
29: static void lsrt_timer_hook(timer *t);
30: static void ackd_timer_hook(timer *t);
31:
32:
33: static void
34: init_lists(struct ospf_proto *p, struct ospf_neighbor *n)
35: {
36: s_init_list(&(n->lsrql));
37: n->lsrqi = SHEAD(n->lsrql);
38: n->lsrqh = ospf_top_new(p, n->pool);
39:
40: s_init_list(&(n->lsrtl));
41: n->lsrth = ospf_top_new(p, n->pool);
42: }
43:
44: static void
45: release_lsrtl(struct ospf_proto *p, struct ospf_neighbor *n)
46: {
47: struct top_hash_entry *ret, *en;
48:
49: WALK_SLIST(ret, n->lsrtl)
50: {
51: en = ospf_hash_find_entry(p->gr, ret);
52: if (en)
53: en->ret_count--;
54: }
55: }
56:
57: /* Resets LSA request and retransmit lists.
58: * We do not reset DB summary list iterator here,
59: * it is reset during entering EXCHANGE state.
60: */
61: static void
62: reset_lists(struct ospf_proto *p, struct ospf_neighbor *n)
63: {
64: release_lsrtl(p, n);
65: ospf_top_free(n->lsrqh);
66: ospf_top_free(n->lsrth);
67: ospf_reset_lsack_queue(n);
68:
69: tm_stop(n->dbdes_timer);
70: tm_stop(n->lsrq_timer);
71: tm_stop(n->lsrt_timer);
72: tm_stop(n->ackd_timer);
73:
74: init_lists(p, n);
75: }
76:
77: struct ospf_neighbor *
78: ospf_neighbor_new(struct ospf_iface *ifa)
79: {
80: struct ospf_proto *p = ifa->oa->po;
81: struct pool *pool = rp_new(p->p.pool, "OSPF Neighbor");
82: struct ospf_neighbor *n = mb_allocz(pool, sizeof(struct ospf_neighbor));
83:
84: n->pool = pool;
85: n->ifa = ifa;
86: add_tail(&ifa->neigh_list, NODE n);
87: n->adj = 0;
88: n->csn = 0;
89: n->state = NEIGHBOR_DOWN;
90:
91: init_lists(p, n);
92: s_init(&(n->dbsi), &(p->lsal));
93:
94: init_list(&n->ackl[ACKL_DIRECT]);
95: init_list(&n->ackl[ACKL_DELAY]);
96:
97: n->inactim = tm_new_set(pool, inactivity_timer_hook, n, 0, 0);
98: n->dbdes_timer = tm_new_set(pool, dbdes_timer_hook, n, 0, ifa->rxmtint);
99: n->lsrq_timer = tm_new_set(pool, lsrq_timer_hook, n, 0, ifa->rxmtint);
100: n->lsrt_timer = tm_new_set(pool, lsrt_timer_hook, n, 0, ifa->rxmtint);
101: n->ackd_timer = tm_new_set(pool, ackd_timer_hook, n, 0, ifa->rxmtint / 2);
102:
103: return (n);
104: }
105:
106: static void
107: ospf_neigh_down(struct ospf_neighbor *n)
108: {
109: struct ospf_iface *ifa = n->ifa;
110: struct ospf_proto *p = ifa->oa->po;
111: u32 rid = n->rid;
112:
113: if ((ifa->type == OSPF_IT_NBMA) || (ifa->type == OSPF_IT_PTMP))
114: {
115: struct nbma_node *nn = find_nbma_node(ifa, n->ip);
116: if (nn)
117: nn->found = 0;
118: }
119:
120: s_get(&(n->dbsi));
121: release_lsrtl(p, n);
122: rem_node(NODE n);
123: rfree(n->pool);
124:
125: OSPF_TRACE(D_EVENTS, "Neighbor %R on %s removed", rid, ifa->ifname);
126: }
127:
128: /**
129: * ospf_neigh_chstate - handles changes related to new or lod state of neighbor
130: * @n: OSPF neighbor
131: * @state: new state
132: *
133: * Many actions have to be taken acording to a change of state of a neighbor. It
134: * starts rxmt timers, call interface state machine etc.
135: */
136: static void
137: ospf_neigh_chstate(struct ospf_neighbor *n, u8 state)
138: {
139: struct ospf_iface *ifa = n->ifa;
140: struct ospf_proto *p = ifa->oa->po;
141: u8 old_state = n->state;
142: int old_fadj = ifa->fadj;
143:
144: if (state == old_state)
145: return;
146:
147: OSPF_TRACE(D_EVENTS, "Neighbor %R on %s changed state from %s to %s",
148: n->rid, ifa->ifname, ospf_ns_names[old_state], ospf_ns_names[state]);
149:
150: n->state = state;
151:
152: /* Increase number of partial adjacencies */
153: if ((state == NEIGHBOR_EXCHANGE) || (state == NEIGHBOR_LOADING))
154: p->padj++;
155:
156: /* Decrease number of partial adjacencies */
157: if ((old_state == NEIGHBOR_EXCHANGE) || (old_state == NEIGHBOR_LOADING))
158: p->padj--;
159:
160: /* Increase number of full adjacencies */
161: if (state == NEIGHBOR_FULL)
162: ifa->fadj++;
163:
164: /* Decrease number of full adjacencies */
165: if (old_state == NEIGHBOR_FULL)
166: ifa->fadj--;
167:
168: if (ifa->fadj != old_fadj)
169: {
170: /* RFC 2328 12.4 Event 4 - neighbor enters/leaves Full state */
171: ospf_notify_rt_lsa(ifa->oa);
172: ospf_notify_net_lsa(ifa);
173:
174: /* RFC 2328 12.4 Event 8 - vlink state change */
175: if (ifa->type == OSPF_IT_VLINK)
176: ospf_notify_rt_lsa(ifa->voa);
177: }
178:
179: if (state == NEIGHBOR_EXSTART)
180: {
181: /* First time adjacency */
182: if (n->adj == 0)
183: n->dds = random_u32();
184:
185: n->dds++;
186: n->myimms = DBDES_IMMS;
187:
188: tm_start(n->dbdes_timer, 0);
189: tm_start(n->ackd_timer, ifa->rxmtint / 2);
190: }
191:
192: if (state > NEIGHBOR_EXSTART)
193: n->myimms &= ~DBDES_I;
194:
195: /* Generate NeighborChange event if needed, see RFC 2328 9.2 */
196: if ((state == NEIGHBOR_2WAY) && (old_state < NEIGHBOR_2WAY))
197: ospf_iface_sm(ifa, ISM_NEICH);
198: if ((state < NEIGHBOR_2WAY) && (old_state >= NEIGHBOR_2WAY))
199: ospf_iface_sm(ifa, ISM_NEICH);
200: }
201:
202: /**
203: * ospf_neigh_sm - ospf neighbor state machine
204: * @n: neighor
205: * @event: actual event
206: *
207: * This part implements the neighbor state machine as described in 10.3 of
208: * RFC 2328. The only difference is that state %NEIGHBOR_ATTEMPT is not
209: * used. We discover neighbors on nonbroadcast networks in the
210: * same way as on broadcast networks. The only difference is in
211: * sending hello packets. These are sent to IPs listed in
212: * @ospf_iface->nbma_list .
213: */
214: void
215: ospf_neigh_sm(struct ospf_neighbor *n, int event)
216: {
217: struct ospf_proto *p = n->ifa->oa->po;
218:
219: DBG("Neighbor state machine for %R on %s, event %s\n",
220: n->rid, n->ifa->ifname, ospf_inm_names[event]);
221:
222: switch (event)
223: {
224: case INM_START:
225: ospf_neigh_chstate(n, NEIGHBOR_ATTEMPT);
226: /* NBMA are used different way */
227: break;
228:
229: case INM_HELLOREC:
230: if (n->state < NEIGHBOR_INIT)
231: ospf_neigh_chstate(n, NEIGHBOR_INIT);
232:
233: /* Restart inactivity timer */
234: tm_start(n->inactim, n->ifa->deadint);
235: break;
236:
237: case INM_2WAYREC:
238: if (n->state < NEIGHBOR_2WAY)
239: ospf_neigh_chstate(n, NEIGHBOR_2WAY);
240: if ((n->state == NEIGHBOR_2WAY) && can_do_adj(n))
241: ospf_neigh_chstate(n, NEIGHBOR_EXSTART);
242: break;
243:
244: case INM_NEGDONE:
245: if (n->state == NEIGHBOR_EXSTART)
246: {
247: ospf_neigh_chstate(n, NEIGHBOR_EXCHANGE);
248:
249: /* Reset DB summary list iterator */
250: s_get(&(n->dbsi));
251: s_init(&(n->dbsi), &p->lsal);
252:
253: /* Add MaxAge LSA entries to retransmission list */
254: ospf_add_flushed_to_lsrt(p, n);
255: }
256: else
257: bug("NEGDONE and I'm not in EXSTART?");
258: break;
259:
260: case INM_EXDONE:
261: if (!EMPTY_SLIST(n->lsrql))
262: ospf_neigh_chstate(n, NEIGHBOR_LOADING);
263: else
264: ospf_neigh_chstate(n, NEIGHBOR_FULL);
265: break;
266:
267: case INM_LOADDONE:
268: ospf_neigh_chstate(n, NEIGHBOR_FULL);
269: break;
270:
271: case INM_ADJOK:
272: /* Can In build adjacency? */
273: if ((n->state == NEIGHBOR_2WAY) && can_do_adj(n))
274: {
275: ospf_neigh_chstate(n, NEIGHBOR_EXSTART);
276: }
277: else if ((n->state >= NEIGHBOR_EXSTART) && !can_do_adj(n))
278: {
279: reset_lists(p, n);
280: ospf_neigh_chstate(n, NEIGHBOR_2WAY);
281: }
282: break;
283:
284: case INM_SEQMIS:
285: case INM_BADLSREQ:
286: if (n->state >= NEIGHBOR_EXCHANGE)
287: {
288: reset_lists(p, n);
289: ospf_neigh_chstate(n, NEIGHBOR_EXSTART);
290: }
291: break;
292:
293: case INM_KILLNBR:
294: case INM_LLDOWN:
295: case INM_INACTTIM:
296: /* No need for reset_lists() */
297: ospf_neigh_chstate(n, NEIGHBOR_DOWN);
298: ospf_neigh_down(n);
299: break;
300:
301: case INM_1WAYREC:
302: reset_lists(p, n);
303: ospf_neigh_chstate(n, NEIGHBOR_INIT);
304: break;
305:
306: default:
307: bug("%s: INM - Unknown event?", p->p.name);
308: break;
309: }
310: }
311:
312: static int
313: can_do_adj(struct ospf_neighbor *n)
314: {
315: struct ospf_iface *ifa = n->ifa;
316: struct ospf_proto *p = ifa->oa->po;
317: int i = 0;
318:
319: switch (ifa->type)
320: {
321: case OSPF_IT_PTP:
322: case OSPF_IT_PTMP:
323: case OSPF_IT_VLINK:
324: i = 1;
325: break;
326: case OSPF_IT_BCAST:
327: case OSPF_IT_NBMA:
328: switch (ifa->state)
329: {
330: case OSPF_IS_DOWN:
331: case OSPF_IS_LOOP:
332: bug("%s: Iface %s in down state?", p->p.name, ifa->ifname);
333: break;
334: case OSPF_IS_WAITING:
335: DBG("%s: Neighbor? on iface %s\n", p->p.name, ifa->ifname);
336: break;
337: case OSPF_IS_DROTHER:
338: if (((n->rid == ifa->drid) || (n->rid == ifa->bdrid))
339: && (n->state >= NEIGHBOR_2WAY))
340: i = 1;
341: break;
342: case OSPF_IS_PTP:
343: case OSPF_IS_BACKUP:
344: case OSPF_IS_DR:
345: if (n->state >= NEIGHBOR_2WAY)
346: i = 1;
347: break;
348: default:
349: bug("%s: Iface %s in unknown state?", p->p.name, ifa->ifname);
350: break;
351: }
352: break;
353: default:
354: bug("%s: Iface %s is unknown type?", p->p.name, ifa->ifname);
355: break;
356: }
357: DBG("%s: Iface %s can_do_adj=%d\n", p->p.name, ifa->ifname, i);
358: return i;
359: }
360:
361:
362: static inline u32 neigh_get_id(struct ospf_proto *p UNUSED4 UNUSED6, struct ospf_neighbor *n)
363: { return ospf_is_v2(p) ? ipa_to_u32(n->ip) : n->rid; }
364:
365: static struct ospf_neighbor *
366: elect_bdr(struct ospf_proto *p, list nl)
367: {
368: struct ospf_neighbor *neigh, *n1, *n2;
369: u32 nid;
370:
371: n1 = NULL;
372: n2 = NULL;
373: WALK_LIST(neigh, nl) /* First try those decl. themselves */
374: {
375: nid = neigh_get_id(p, neigh);
376:
377: if (neigh->state >= NEIGHBOR_2WAY) /* Higher than 2WAY */
378: if (neigh->priority > 0) /* Eligible */
379: if (neigh->dr != nid) /* And not decl. itself DR */
380: {
381: if (neigh->bdr == nid) /* Declaring BDR */
382: {
383: if (n1 != NULL)
384: {
385: if (neigh->priority > n1->priority)
386: n1 = neigh;
387: else if (neigh->priority == n1->priority)
388: if (neigh->rid > n1->rid)
389: n1 = neigh;
390: }
391: else
392: {
393: n1 = neigh;
394: }
395: }
396: else /* And NOT declaring BDR */
397: {
398: if (n2 != NULL)
399: {
400: if (neigh->priority > n2->priority)
401: n2 = neigh;
402: else if (neigh->priority == n2->priority)
403: if (neigh->rid > n2->rid)
404: n2 = neigh;
405: }
406: else
407: {
408: n2 = neigh;
409: }
410: }
411: }
412: }
413: if (n1 == NULL)
414: n1 = n2;
415:
416: return (n1);
417: }
418:
419: static struct ospf_neighbor *
420: elect_dr(struct ospf_proto *p, list nl)
421: {
422: struct ospf_neighbor *neigh, *n;
423: u32 nid;
424:
425: n = NULL;
426: WALK_LIST(neigh, nl) /* And now DR */
427: {
428: nid = neigh_get_id(p, neigh);
429:
430: if (neigh->state >= NEIGHBOR_2WAY) /* Higher than 2WAY */
431: if (neigh->priority > 0) /* Eligible */
432: if (neigh->dr == nid) /* And declaring itself DR */
433: {
434: if (n != NULL)
435: {
436: if (neigh->priority > n->priority)
437: n = neigh;
438: else if (neigh->priority == n->priority)
439: if (neigh->rid > n->rid)
440: n = neigh;
441: }
442: else
443: {
444: n = neigh;
445: }
446: }
447: }
448:
449: return (n);
450: }
451:
452: /**
453: * ospf_dr_election - (Backup) Designed Router election
454: * @ifa: actual interface
455: *
456: * When the wait timer fires, it is time to elect (Backup) Designated Router.
457: * Structure describing me is added to this list so every electing router has
458: * the same list. Backup Designated Router is elected before Designated
459: * Router. This process is described in 9.4 of RFC 2328. The function is
460: * supposed to be called only from ospf_iface_sm() as a part of the interface
461: * state machine.
462: */
463: void
464: ospf_dr_election(struct ospf_iface *ifa)
465: {
466: struct ospf_proto *p = ifa->oa->po;
467: struct ospf_neighbor *neigh, *ndr, *nbdr, me;
468: u32 myid = p->router_id;
469:
470: DBG("(B)DR election.\n");
471:
472: me.state = NEIGHBOR_2WAY;
473: me.rid = myid;
474: me.priority = ifa->priority;
475: me.ip = ifa->addr->ip;
476:
477: me.dr = ospf_is_v2(p) ? ipa_to_u32(ifa->drip) : ifa->drid;
478: me.bdr = ospf_is_v2(p) ? ipa_to_u32(ifa->bdrip) : ifa->bdrid;
479: me.iface_id = ifa->iface_id;
480:
481: add_tail(&ifa->neigh_list, NODE & me);
482:
483: nbdr = elect_bdr(p, ifa->neigh_list);
484: ndr = elect_dr(p, ifa->neigh_list);
485:
486: if (ndr == NULL)
487: ndr = nbdr;
488:
489: /* 9.4. (4) */
490: if (((ifa->drid == myid) && (ndr != &me))
491: || ((ifa->drid != myid) && (ndr == &me))
492: || ((ifa->bdrid == myid) && (nbdr != &me))
493: || ((ifa->bdrid != myid) && (nbdr == &me)))
494: {
495: me.dr = ndr ? neigh_get_id(p, ndr) : 0;
496: me.bdr = nbdr ? neigh_get_id(p, nbdr) : 0;
497:
498: nbdr = elect_bdr(p, ifa->neigh_list);
499: ndr = elect_dr(p, ifa->neigh_list);
500:
501: if (ndr == NULL)
502: ndr = nbdr;
503: }
504:
505: rem_node(NODE & me);
506:
507:
508: u32 old_drid = ifa->drid;
509: u32 old_bdrid = ifa->bdrid;
510:
511: ifa->drid = ndr ? ndr->rid : 0;
512: ifa->drip = ndr ? ndr->ip : IPA_NONE;
513: ifa->dr_iface_id = ndr ? ndr->iface_id : 0;
514:
515: ifa->bdrid = nbdr ? nbdr->rid : 0;
516: ifa->bdrip = nbdr ? nbdr->ip : IPA_NONE;
517:
518: DBG("DR=%R, BDR=%R\n", ifa->drid, ifa->bdrid);
519:
520: /* We are part of the interface state machine */
521: if (ifa->drid == myid)
522: ospf_iface_chstate(ifa, OSPF_IS_DR);
523: else if (ifa->bdrid == myid)
524: ospf_iface_chstate(ifa, OSPF_IS_BACKUP);
525: else
526: ospf_iface_chstate(ifa, OSPF_IS_DROTHER);
527:
528: /* Review neighbor adjacencies if DR or BDR changed */
529: if ((ifa->drid != old_drid) || (ifa->bdrid != old_bdrid))
530: WALK_LIST(neigh, ifa->neigh_list)
531: if (neigh->state >= NEIGHBOR_2WAY)
532: ospf_neigh_sm(neigh, INM_ADJOK);
533:
534: /* RFC 2328 12.4 Event 3 - DR change */
535: if (ifa->drid != old_drid)
536: ospf_notify_rt_lsa(ifa->oa);
537: }
538:
539: struct ospf_neighbor *
540: find_neigh(struct ospf_iface *ifa, u32 rid)
541: {
542: struct ospf_neighbor *n;
543: WALK_LIST(n, ifa->neigh_list)
544: if (n->rid == rid)
545: return n;
546: return NULL;
547: }
548:
549: struct ospf_neighbor *
550: find_neigh_by_ip(struct ospf_iface *ifa, ip_addr ip)
551: {
552: struct ospf_neighbor *n;
553: WALK_LIST(n, ifa->neigh_list)
554: if (ipa_equal(n->ip, ip))
555: return n;
556: return NULL;
557: }
558:
559: static void
560: inactivity_timer_hook(timer * timer)
561: {
562: struct ospf_neighbor *n = (struct ospf_neighbor *) timer->data;
563: struct ospf_proto *p = n->ifa->oa->po;
564:
565: OSPF_TRACE(D_EVENTS, "Inactivity timer expired for nbr %R on %s",
566: n->rid, n->ifa->ifname);
567: ospf_neigh_sm(n, INM_INACTTIM);
568: }
569:
570: static void
571: ospf_neigh_bfd_hook(struct bfd_request *req)
572: {
573: struct ospf_neighbor *n = req->data;
574: struct ospf_proto *p = n->ifa->oa->po;
575:
576: if (req->down)
577: {
578: OSPF_TRACE(D_EVENTS, "BFD session down for nbr %R on %s",
579: n->rid, n->ifa->ifname);
580: ospf_neigh_sm(n, INM_INACTTIM);
581: }
582: }
583:
584: void
585: ospf_neigh_update_bfd(struct ospf_neighbor *n, int use_bfd)
586: {
1.1.1.2 ! misho 587: struct ospf_proto *p = n->ifa->oa->po;
! 588:
1.1 misho 589: if (use_bfd && !n->bfd_req)
1.1.1.2 ! misho 590: n->bfd_req = bfd_request_session(n->pool, n->ip, n->ifa->addr->ip,
! 591: n->ifa->iface, p->p.vrf,
1.1 misho 592: ospf_neigh_bfd_hook, n);
593:
594: if (!use_bfd && n->bfd_req)
595: {
596: rfree(n->bfd_req);
597: n->bfd_req = NULL;
598: }
599: }
600:
601:
602: static void
603: dbdes_timer_hook(timer *t)
604: {
605: struct ospf_neighbor *n = t->data;
606: struct ospf_proto *p = n->ifa->oa->po;
607:
608: // OSPF_TRACE(D_EVENTS, "DBDES timer expired for nbr %R on %s", n->rid, n->ifa->ifname);
609:
610: if (n->state == NEIGHBOR_EXSTART)
611: ospf_send_dbdes(p, n);
612:
613: if ((n->state == NEIGHBOR_EXCHANGE) && (n->myimms & DBDES_MS))
614: ospf_rxmt_dbdes(p, n);
615: }
616:
617: static void
618: lsrq_timer_hook(timer *t)
619: {
620: struct ospf_neighbor *n = t->data;
621: struct ospf_proto *p = n->ifa->oa->po;
622:
623: // OSPF_TRACE(D_EVENTS, "LSRQ timer expired for nbr %R on %s", n->rid, n->ifa->ifname);
624:
625: if ((n->state >= NEIGHBOR_EXCHANGE) && !EMPTY_SLIST(n->lsrql))
626: ospf_send_lsreq(p, n);
627: }
628:
629: static void
630: lsrt_timer_hook(timer *t)
631: {
632: struct ospf_neighbor *n = t->data;
633: struct ospf_proto *p = n->ifa->oa->po;
634:
635: // OSPF_TRACE(D_EVENTS, "LSRT timer expired for nbr %R on %s", n->rid, n->ifa->ifname);
636:
637: if ((n->state >= NEIGHBOR_EXCHANGE) && !EMPTY_SLIST(n->lsrtl))
638: ospf_rxmt_lsupd(p, n);
639: }
640:
641: static void
642: ackd_timer_hook(timer *t)
643: {
644: struct ospf_neighbor *n = t->data;
645: struct ospf_proto *p = n->ifa->oa->po;
646:
647: ospf_send_lsack(p, n, ACKL_DELAY);
648: }
649:
650:
651: void
652: ospf_sh_neigh_info(struct ospf_neighbor *n)
653: {
654: struct ospf_iface *ifa = n->ifa;
655: char *pos = "PtP ";
656: char etime[6];
657: int exp, sec, min;
658:
659: exp = n->inactim->expires - now;
660: sec = exp % 60;
661: min = exp / 60;
662: if (min > 59)
663: {
664: bsprintf(etime, "-Inf-");
665: }
666: else
667: {
668: bsprintf(etime, "%02u:%02u", min, sec);
669: }
670:
671: if ((ifa->type == OSPF_IT_BCAST) || (ifa->type == OSPF_IT_NBMA))
672: {
673: if (n->rid == ifa->drid)
674: pos = "DR ";
675: else if (n->rid == ifa->bdrid)
676: pos = "BDR ";
677: else
678: pos = "Other";
679: }
680:
681: cli_msg(-1013, "%-1R\t%3u\t%s/%s\t%-5s\t%-10s %-1I", n->rid, n->priority,
682: ospf_ns_names[n->state], pos, etime, ifa->ifname, n->ip);
683: }
FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>