[PATCH 1/1] * Implement per-table protocol hooks in core

Alexander V. Chernikov melifaro at ipfw.ru
Fri Nov 25 16:39:48 CET 2011


---
 nest/proto.c      |  132 ++++++++++++++++++++++++++++++++++++++++-------------
 nest/protocol.h   |   16 ++----
 nest/route.h      |    3 +
 nest/rt-table.c   |   64 ++++++++++---------------
 proto/pipe/pipe.c |   68 ++++++++++++++++++++++++++-
 proto/pipe/pipe.h |    7 +--
 6 files changed, 201 insertions(+), 89 deletions(-)

diff --git a/nest/proto.c b/nest/proto.c
index d55c348..3d7684c 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -19,6 +19,9 @@
 #include "nest/iface.h"
 #include "nest/cli.h"
 #include "filter/filter.h"
+#ifdef CONFIG_PIPE
+#include "proto/pipe/pipe.h"
+#endif
 
 pool *proto_pool;
 
@@ -112,8 +115,6 @@ proto_new(struct proto_config *c, unsigned size)
   p->disabled = c->disabled;
   p->proto = pr;
   p->table = c->table->table;
-  p->in_filter = c->in_filter;
-  p->out_filter = c->out_filter;
   p->hash_key = random_u32();
   c->proto = p;
   return p;
@@ -142,6 +143,8 @@ proto_init_instance(struct proto *p)
  * protocol does), you needn't to worry about this function since the
  * connection to the protocol's primary routing table is initialized
  * automatically by the core code.
+ *
+ * Returns pointer to announce hook or NULL
  */
 struct announce_hook *
 proto_add_announce_hook(struct proto *p, struct rtable *t)
@@ -152,7 +155,7 @@ proto_add_announce_hook(struct proto *p, struct rtable *t)
     return NULL;
   DBG("Connecting protocol %s to table %s\n", p->name, t->name);
   PD(p, "Connected to table %s", t->name);
-  h = mb_alloc(p->pool, sizeof(struct announce_hook));
+  h = mb_allocz(p->pool, sizeof(struct announce_hook));
   h->table = t;
   h->proto = p;
   h->next = p->ahooks;
@@ -161,6 +164,25 @@ proto_add_announce_hook(struct proto *p, struct rtable *t)
   return h;
 }
 
+/**
+ * proto_find_announce_hook - find announce hooks
+ * @p: protocol instance
+ * @t: routing table
+ *
+ * Returns pointer to announce hook or NULL
+ */
+struct announce_hook *
+proto_find_announce_hook(struct proto *p, struct rtable *t)
+{
+  struct announce_hook *a;
+
+  for (a = p->ahooks; a; a = a->next)
+    if (a->table == t)
+      return a;
+
+  return NULL;
+}
+
 static void
 proto_flush_hooks(struct proto *p)
 {
@@ -324,6 +346,8 @@ proto_init(struct proto_config *c)
 static int
 proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config *nc, int type)
 {
+  struct announce_hook *a;
+
   /* If the protocol is DOWN, we just restart it */
   if (p->proto_state == PS_DOWN)
     return 0;
@@ -353,6 +377,19 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config
   p->debug = nc->debug;
   p->mrtdump = nc->mrtdump;
 
+  /*
+   * Set import/export filters if protocol is connected to the route table.
+   * We need to to this before protocol-specific reconfigure hook due to
+   * possible filter manipulations in pipe-like protocols.
+   * If no connection to master table is available, skip this step.
+   * In this case filters are assigned in proto_feed_initial
+   */
+  if (a = p->thook)
+    {
+      a->in_filter = nc->in_filter;
+      a->out_filter = nc->out_filter;
+    }
+
   /* Execute protocol specific reconfigure hook */
   if (! (p->proto->reconfigure && p->proto->reconfigure(p, nc)))
     return 0;
@@ -361,8 +398,6 @@ proto_reconfigure(struct proto *p, struct proto_config *oc, struct proto_config
   PD(p, "Reconfigured");
   p->cf = nc;
   p->name = nc->name;
-  p->in_filter = nc->in_filter;
-  p->out_filter = nc->out_filter;
   p->preference = nc->preference;
 
   if (import_changed || export_changed)
@@ -552,6 +587,7 @@ void
 protos_dump_all(void)
 {
   struct proto *p;
+  struct announce_hook *a;
 
   debug("Protocols:\n");
 
@@ -559,10 +595,14 @@ protos_dump_all(void)
     {
       debug("  protocol %s state %s/%s\n", p->name,
 	    p_states[p->proto_state], c_states[p->core_state]);
-      if (p->in_filter)
-	debug("\tInput filter: %s\n", filter_name(p->in_filter));
-      if (p->out_filter != FILTER_REJECT)
-	debug("\tOutput filter: %s\n", filter_name(p->out_filter));
+      for (a = p->ahooks; a; a = a->next)
+	{
+	  debug("\tTABLE %s\n", a->table->name);
+	  if (a->in_filter)
+	    debug("\tInput filter: %s\n", filter_name(a->in_filter));
+	  if (a->out_filter != FILTER_REJECT)
+	    debug("\tOutput filter: %s\n", filter_name(a->out_filter));
+	}
       if (p->disabled)
 	debug("\tDISABLED\n");
       else if (p->proto->dump)
@@ -680,12 +720,30 @@ static void
 proto_feed_initial(void *P)
 {
   struct proto *p = P;
+  struct announce_hook *a;
 
   if (p->core_state != FS_FEEDING)
     return;
 
   DBG("Feeding protocol %s\n", p->name);
-  proto_add_announce_hook(p, p->table);
+  if (a = proto_add_announce_hook(p, p->table))
+    {
+      /* Set master table */
+      p->thook = a;
+      /* Set filters */
+      a->in_filter = p->cf->in_filter;
+      a->out_filter = p->cf->out_filter;
+      /* Set pointer to stats */
+      a->stats = &p->stats;
+    }
+
+  /*
+   * Call reconfigure hook to permit possible filter changes.
+   * Old config is the same as new so no problems should arise
+   */
+  if (p->proto->reconfigure)
+    p->proto->reconfigure(p, p->cf);
+
   if_feed_baby(p);
   proto_feed_more(P);
 }
@@ -854,9 +912,8 @@ proto_state_name(struct proto *p)
 }
 
 static void
-proto_do_show_stats(struct proto *p)
+proto_do_show_stats(struct proto_stats *s)
 {
-  struct proto_stats *s = &p->stats;
   cli_msg(-1006, "  Routes:         %u imported, %u exported, %u preferred", 
 	  s->imp_routes, s->exp_routes, s->pref_routes);
   cli_msg(-1006, "  Route change stats:     received   rejected   filtered    ignored   accepted");
@@ -875,11 +932,12 @@ proto_do_show_stats(struct proto *p)
 }
 
 #ifdef CONFIG_PIPE
-static void
-proto_do_show_pipe_stats(struct proto *p)
+void
+pipe_do_show_stats(struct proto *P)
 {
-  struct proto_stats *s1 = &p->stats;
-  struct proto_stats *s2 = pipe_get_peer_stats(p);
+  struct pipe_proto *p = (struct pipe_proto *) P;
+  struct proto_stats *s1 = &P->stats;
+  struct proto_stats *s2 = &p->peer_stats;
 
   /*
    * Pipe stats (as anything related to pipes) are a bit tricky. There
@@ -899,20 +957,20 @@ proto_do_show_pipe_stats(struct proto *p)
    */
 
   cli_msg(-1006, "  Routes:         %u imported, %u exported", 
-	  s2->imp_routes, s1->imp_routes);
+	  s1->imp_routes, s2->imp_routes);
   cli_msg(-1006, "  Route change stats:     received   rejected   filtered    ignored   accepted");
   cli_msg(-1006, "    Import updates:     %10u %10u %10u %10u %10u",
-	  s2->exp_updates_received, s2->exp_updates_rejected + s2->imp_updates_invalid,
-	  s2->exp_updates_filtered, s2->imp_updates_ignored, s2->imp_updates_accepted);
+	  s2->exp_updates_received, s2->exp_updates_rejected + s1->imp_updates_invalid,
+	  s2->exp_updates_filtered, s1->imp_updates_ignored, s1->imp_updates_accepted);
   cli_msg(-1006, "    Import withdraws:   %10u %10u        --- %10u %10u",
-	  s2->exp_withdraws_received, s2->imp_withdraws_invalid,
-	  s2->imp_withdraws_ignored, s2->imp_withdraws_accepted);
+	  s2->exp_withdraws_received, s1->imp_withdraws_invalid,
+	  s1->imp_withdraws_ignored, s1->imp_withdraws_accepted);
   cli_msg(-1006, "    Export updates:     %10u %10u %10u %10u %10u",
-	  s1->exp_updates_received, s1->exp_updates_rejected + s1->imp_updates_invalid,
-	  s1->exp_updates_filtered, s1->imp_updates_ignored, s1->imp_updates_accepted);
+	  s1->exp_updates_received, s1->exp_updates_rejected + s2->imp_updates_invalid,
+	  s1->exp_updates_filtered, s2->imp_updates_ignored, s2->imp_updates_accepted);
   cli_msg(-1006, "    Export withdraws:   %10u %10u        --- %10u %10u",
-	  s1->exp_withdraws_received, s1->imp_withdraws_invalid,
-	  s1->imp_withdraws_ignored, s1->imp_withdraws_accepted);
+	  s1->exp_withdraws_received, s2->imp_withdraws_invalid,
+	  s2->imp_withdraws_ignored, s2->imp_withdraws_accepted);
 }
 #endif
 
@@ -920,6 +978,7 @@ void
 proto_cmd_show(struct proto *p, unsigned int verbose, int cnt)
 {
   byte buf[256], tbuf[TM_DATETIME_BUFFER_SIZE];
+  struct announce_hook *a;
 
   /* First protocol - show header */
   if (!cnt)
@@ -943,17 +1002,26 @@ proto_cmd_show(struct proto *p, unsigned int verbose, int cnt)
       if (p->cf->router_id)
 	cli_msg(-1006, "  Router ID:      %R", p->cf->router_id);
       cli_msg(-1006, "  Preference:     %d", p->preference);
-      cli_msg(-1006, "  Input filter:   %s", filter_name(p->in_filter));
-      cli_msg(-1006, "  Output filter:  %s", filter_name(p->out_filter));
 
-      if (p->proto_state != PS_DOWN)
+      for (a = p->ahooks; a; a = a->next)
 	{
+	  cli_msg(-1006, "  Table:   %s", a->table->name);
+	  cli_msg(-1006, "  Input filter:   %s", filter_name(a->in_filter));
+	  cli_msg(-1006, "  Output filter:  %s", filter_name(a->out_filter));
+
+	  if (p->proto_state != PS_DOWN)
+	    {
 #ifdef CONFIG_PIPE
-	  if (proto_is_pipe(p))
-	    proto_do_show_pipe_stats(p);
-	  else
+	      /* XXX: This block should be removed */
+	      if (proto_is_pipe(p))
+		{
+		  if (a == p->thook)
+		    pipe_do_show_stats(p);
+		}
+	      else
 #endif
-	    proto_do_show_stats(p);
+		proto_do_show_stats(a->stats);
+	    }
 	}
 
       if (p->proto->show_proto_info)
diff --git a/nest/protocol.h b/nest/protocol.h
index a7518c2..09ccd9c 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -190,8 +190,7 @@ struct proto {
   void (*rte_remove)(struct network *, struct rte *);
 
   struct rtable *table;			/* Our primary routing table */
-  struct filter *in_filter;		/* Input filter */
-  struct filter *out_filter;		/* Output filter */
+  struct announce_hook *thook;		/* Announcement hook for the master table  */
   struct announce_hook *ahooks;		/* Announcement hooks for this protocol */
 
   struct fib_iterator *feed_iterator;	/* Routing table iterator used during protocol feeding */
@@ -349,18 +348,13 @@ struct announce_hook {
   node n;
   struct rtable *table;
   struct proto *proto;
+  struct filter *in_filter;		/* Input filter */
+  struct filter *out_filter;		/* Output filter */
+  struct proto_stats *stats;		/* Per-table protocol statistics */
   struct announce_hook *next;		/* Next hook for the same protocol */
 };
 
 struct announce_hook *proto_add_announce_hook(struct proto *, struct rtable *);
-
-/*
- *	Some pipe-specific nest hacks
- */
-
-#ifdef CONFIG_PIPE
-#include "proto/pipe/pipe.h"
-#endif
-
+struct announce_hook *proto_find_announce_hook(struct proto *p, struct rtable *t);
 
 #endif
diff --git a/nest/route.h b/nest/route.h
index a4c0154..ff09178 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -218,6 +218,7 @@ typedef struct rte {
 #define RA_ANY 2			/* Announcement of any route change */
 
 struct config;
+struct announce_hook;
 
 void rt_init(void);
 void rt_preconfig(struct config *);
@@ -230,6 +231,8 @@ static inline net *net_get(rtable *tab, ip_addr addr, unsigned len) { return (ne
 rte *rte_find(net *net, struct proto *p);
 rte *rte_get_temp(struct rta *);
 void rte_update(rtable *tab, net *net, struct proto *p, struct proto *src, rte *new);
+#define rte_update(tab, net, p, src, new)	rte_do_update(tab, (p)->thook, net, p, src, new)
+void rte_do_update(rtable *tab, struct announce_hook *a, net *net, struct proto *p, struct proto *src, rte *new);
 void rte_discard(rtable *tab, rte *old);
 void rte_dump(rte *);
 void rte_free(rte *);
diff --git a/nest/rt-table.c b/nest/rt-table.c
index e20d2f6..53578c0 100644
--- a/nest/rt-table.c
+++ b/nest/rt-table.c
@@ -187,21 +187,12 @@ static inline void
 do_rte_announce(struct announce_hook *a, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed)
 {
   struct proto *p = a->proto;
-  struct filter *filter = p->out_filter;
-  struct proto_stats *stats = &p->stats;
+  struct filter *filter = a->out_filter;
+  struct proto_stats *stats = a->stats;
   rte *new0 = new;
   rte *old0 = old;
   int ok;
 
-#ifdef CONFIG_PIPE
-  /* The secondary direction of the pipe */
-  if (proto_is_pipe(p) && (p->table != a->table))
-    {
-      filter = p->in_filter;
-      stats = pipe_get_peer_stats(p);
-    }
-#endif
-
   if (new)
     {
       stats->exp_updates_received++;
@@ -423,18 +414,14 @@ rte_same(rte *x, rte *y)
 }
 
 static void
-rte_recalculate(rtable *table, net *net, struct proto *p, struct proto *src, rte *new, ea_list *tmpa)
+rte_recalculate(rtable *table, struct announce_hook *a, net *net, struct proto *p, struct proto *src, rte *new, ea_list *tmpa)
 {
-  struct proto_stats *stats = &p->stats;
+  struct proto_stats *stats;
   rte *old_best = net->routes;
   rte *old = NULL;
   rte **k, *r, *s;
 
-#ifdef CONFIG_PIPE
-  if (proto_is_pipe(p) && (p->table == table))
-    stats = pipe_get_peer_stats(p);
-#endif
-
+  stats = a ? a->stats : &p->stats;
   k = &net->routes;			/* Find and remove original route from the same protocol */
   while (old = *k)
     {
@@ -607,8 +594,9 @@ rte_update_unlock(void)
 }
 
 /**
- * rte_update - enter a new update to a routing table
+ * rte_do_update - enter a new update to a routing table
  * @table: table to be updated
+ * @a: pointer to table announce hook
  * @net: network node
  * @p: protocol submitting the update
  * @src: protocol originating the update
@@ -648,28 +636,27 @@ rte_update_unlock(void)
  */
 
 void
-rte_update(rtable *table, net *net, struct proto *p, struct proto *src, rte *new)
+rte_do_update(rtable *table, struct announce_hook *a, net *net, struct proto *p, struct proto *src, rte *new)
 {
   ea_list *tmpa = NULL;
-  struct proto_stats *stats = &p->stats;
+  struct proto_stats *stats;
+  struct filter *filter;
 
-#ifdef CONFIG_PIPE
-  if (proto_is_pipe(p) && (p->table == table))
-    stats = pipe_get_peer_stats(p);
-#endif
+  if (a)
+    {
+      stats = a->stats;
+      filter = a->in_filter;
+    }
+  else
+    {
+      stats = &p->stats;
+      filter = p->cf->in_filter;
+    }
 
   rte_update_lock();
   if (new)
     {
       new->sender = p;
-      struct filter *filter = p->in_filter;
-
-      /* Do not filter routes going through the pipe, 
-	 they are filtered in the export filter only. */
-#ifdef CONFIG_PIPE
-      if (proto_is_pipe(p))
-	filter = FILTER_ACCEPT;
-#endif
 
       stats->imp_updates_received++;
       if (!rte_validate(new))
@@ -706,13 +693,13 @@ rte_update(rtable *table, net *net, struct proto *p, struct proto *src, rte *new
   else
     stats->imp_withdraws_received++;
 
-  rte_recalculate(table, net, p, src, new, tmpa);
+  rte_recalculate(table, a, net, p, src, new, tmpa);
   rte_update_unlock();
   return;
 
 drop:
   rte_free(new);
-  rte_recalculate(table, net, p, src, NULL, NULL);
+  rte_recalculate(table, a, net, p, src, NULL, NULL);
   rte_update_unlock();
 }
 
@@ -735,7 +722,7 @@ void
 rte_discard(rtable *t, rte *old)	/* Non-filtered route deletion, used during garbage collection */
 {
   rte_update_lock();
-  rte_recalculate(t, old->net, old->sender, old->attrs->proto, NULL, NULL);
+  rte_recalculate(t, old->sender->thook, old->net, old->sender, old->attrs->proto, NULL, NULL);
   rte_update_unlock();
 }
 
@@ -1680,6 +1667,7 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
 {
   rte *e, *ee;
   byte ia[STD_ADDRESS_P_LENGTH+8];
+  struct announce_hook *a;
   int ok;
 
   bsprintf(ia, "%I/%d", n->n.prefix, n->n.pxlen);
@@ -1709,8 +1697,8 @@ rt_show_net(struct cli *c, net *n, struct rt_show_data *d)
 		 'configure soft' command may change the export filter
 		 and do not update routes */
 
-	      if ((p1->out_filter == FILTER_REJECT) ||
-		  (p1->out_filter && f_run(p1->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT))
+	      if ((a = proto_find_announce_hook(p1, d->table)) && ((a->out_filter == FILTER_REJECT) ||
+		  (a->out_filter && f_run(a->out_filter, &e, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT)))
 		ok = 0;
 	    }
 	}
diff --git a/proto/pipe/pipe.c b/proto/pipe/pipe.c
index 420c5a9..869ddb8 100644
--- a/proto/pipe/pipe.c
+++ b/proto/pipe/pipe.c
@@ -24,6 +24,7 @@
 #include "nest/iface.h"
 #include "nest/protocol.h"
 #include "nest/route.h"
+#include "nest/cli.h"
 #include "conf/conf.h"
 #include "filter/filter.h"
 #include "lib/string.h"
@@ -34,8 +35,9 @@ static void
 pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, ea_list *attrs)
 {
   struct pipe_proto *p = (struct pipe_proto *) P;
-  rtable *dest = (src_table == P->table) ? p->peer : P->table; /* The other side of the pipe */
+  rtable *dest;
   struct proto *src;
+  struct announce_hook *h;
 
   net *nn;
   rte *e;
@@ -44,6 +46,17 @@ pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, e
   if (!new && !old)
     return;
 
+  if (src_table == P->table)
+    {
+      dest = p->peer;
+      h = p->a;
+    }
+  else
+    {
+      dest = P->table;
+      h = P->thook;
+    }
+
   if (dest->pipe_busy)
     {
       log(L_ERR "Pipe loop detected when sending %I/%d to table %s",
@@ -85,7 +98,7 @@ pipe_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, e
     }
 
   src_table->pipe_busy = 1;
-  rte_update(dest, nn, &p->p, (p->mode == PIPE_OPAQUE) ? &p->p : src, e);
+  rte_do_update(dest, h, nn, &p->p, (p->mode == PIPE_OPAQUE) ? &p->p : src, e);
   src_table->pipe_busy = 0;
 }
 
@@ -126,6 +139,10 @@ pipe_start(struct proto *P)
 
   /* Connect the protocol also to the peer routing table. */
   a = proto_add_announce_hook(P, p->peer);
+  p->a = a;
+
+  /* Set up filters and stats */
+  a->stats = &p->peer_stats;
 
   return PS_UP;
 }
@@ -168,13 +185,57 @@ pipe_postconfig(struct proto_config *C)
 static int
 pipe_reconfigure(struct proto *P, struct proto_config *new)
 {
-  // struct pipe_proto *p = (struct pipe_proto *) P;
+  struct announce_hook *a, *pa;
+  struct pipe_proto *p = (struct pipe_proto *)P;
   struct pipe_config *o = (struct pipe_config *) P->cf;
   struct pipe_config *n = (struct pipe_config *) new;
 
   if ((o->peer->table != n->peer->table) || (o->mode != n->mode))
     return 0;
 
+  /*
+   * reconfigure can be called in any protocol status
+   */
+  if (!((a = P->thook) && (pa = p->a)))
+    return 1;
+
+  /* Update filters */
+  /*
+   * Export filter remains as is and import filter moves to
+   * peer table
+   *
+   * /------\                                      /------\
+   * |      | --- (OUT)(1)            (OUT)(3) --- |      |
+   * | MAIN |               | PIPE |               | PEER |
+   * |      | ---- (IN)(2)             (IN)(4) --- |      |
+   * \------/                                      \------/
+   *
+   * When new route is announced on MAIN table it gets checked by
+   * export filter (1), and, after that, it is announced to peer table
+   * via rte_update. import filter (4) is called. When new route is
+   * annouced in PEER table (3) and (2) are used. Oviously, there is
+   * no need in filtering the same route twice, so (4) and (2) filters
+   * should be set to pass all routes.
+   *
+   * User can configure (1) and (2) filters so we move filter (2) to
+   * (3) and set (2) and (4) to FILTER_ACCEPT
+   *
+   * This is the right place to do it since
+   * 1) configure hook is called exactly before initial feeding
+   * 2) it is (now) called exactly after setting new filters in proto_reconfigure
+   */
+
+  /*
+   * Check for changed filters:
+   * Import and export filters are checked in proto_reconfigure by
+   * comparing old/new configurations so no additional checks are
+   * required here
+   */
+
+  pa->out_filter = a->in_filter;
+  a->in_filter = FILTER_ACCEPT;
+  pa->in_filter = FILTER_ACCEPT;
+
   return 1;
 }
 
@@ -194,6 +255,7 @@ pipe_get_status(struct proto *P, byte *buf)
 }
 
 
+
 struct protocol proto_pipe = {
   name:		"Pipe",
   template:	"pipe%d",
diff --git a/proto/pipe/pipe.h b/proto/pipe/pipe.h
index fbd2129..69df612 100644
--- a/proto/pipe/pipe.h
+++ b/proto/pipe/pipe.h
@@ -22,6 +22,7 @@ struct pipe_proto {
   struct proto p;
   struct rtable *peer;
   struct proto_stats peer_stats;	/* Statistics for the direction peer->primary */
+  struct announce_hook *a;		/* Announce hook for the peer table */
   int mode;				/* PIPE_OPAQUE or PIPE_TRANSPARENT */
 };
 
@@ -31,10 +32,6 @@ extern struct protocol proto_pipe;
 static inline int proto_is_pipe(struct proto *p)
 { return p->proto == &proto_pipe; }
 
-static inline struct rtable * pipe_get_peer_table(struct proto *P)
-{ return ((struct pipe_proto *) P)->peer; }
-
-static inline struct proto_stats * pipe_get_peer_stats(struct proto *P)
-{ return &((struct pipe_proto *) P)->peer_stats; }
+void pipe_do_show_stats(struct proto *P);
 
 #endif
-- 
1.7.3.2


--------------070306020804010102050307--



More information about the Bird-users mailing list