From 03253d8c04b7a7901f2bab00847b1893f56ff3d2 Mon Sep 17 00:00:00 2001
From: "Alexander V. Chernikov" <melifaro@yandex-team.ru>
Date: Sun, 22 Jan 2017 17:10:28 +0400
Subject: [PATCH 1/1] Introduce generic aggregation protocol.

---
 configure.in          |   2 +-
 doc/bird.sgml         |  58 ++++
 nest/proto.c          |   3 +
 nest/protocol.h       |   3 +-
 nest/route.h          |   1 +
 proto/agg/Doc         |   2 +
 proto/agg/Makefile    |   5 +
 proto/agg/agg.c       | 718 ++++++++++++++++++++++++++++++++++++++++++++++++++
 proto/agg/agg.h       | 109 ++++++++
 proto/agg/agg_attrs.c | 375 ++++++++++++++++++++++++++
 proto/agg/config.Y    |  95 +++++++
 proto/bgp/attrs.c     |   4 +-
 proto/bgp/bgp.h       |   2 +
 sysdep/autoconf.h.in  |   1 +
 14 files changed, 1374 insertions(+), 4 deletions(-)
 create mode 100644 proto/agg/Doc
 create mode 100644 proto/agg/Makefile
 create mode 100644 proto/agg/agg.c
 create mode 100644 proto/agg/agg.h
 create mode 100644 proto/agg/agg_attrs.c
 create mode 100644 proto/agg/config.Y

diff --git a/configure.in b/configure.in
index 57fa007..892c2ab 100644
--- a/configure.in
+++ b/configure.in
@@ -207,7 +207,7 @@ fi
 
 AC_SUBST(iproutedir)
 
-all_protocols="$proto_bfd bgp ospf pipe $proto_radv rip static"
+all_protocols="$proto_bfd bgp ospf pipe $proto_radv rip static agg"
 if test "$ip" = ipv6 ; then
    all_protocols="$all_protocols babel"
 fi
diff --git a/doc/bird.sgml b/doc/bird.sgml
index 6af0e0f..818a00a 100644
--- a/doc/bird.sgml
+++ b/doc/bird.sgml
@@ -1788,6 +1788,64 @@ protocol bfd {
 </code>
 
 
+<sect>Aggregator
+<label id="agg">
+
+<p>Aggregator protocol is not a real routing protocol. It generates summary routes of
+given protocol type. Currently BGP and static protocols are supported.
+
+<p>Protocol aggregates matching more-specific routes regardless of their type. The only exception
+are unreachable routes - they are not considered as valid routes. One may use protocol
+export filter to tweak routes.
+
+<p>If new configuration containes new summary route or filter change, protocol refeed is triggered.
+
+<p>Nested aggregated routes should not be used within single protocol instance.
+
+<sect1>Configuration
+<label id="agg-config">
+
+<p>Main part of configuration contains emulated protocol type and additional data.
+
+<code>
+protocol aggregator &lt;name&gt; {
+        type bgp as &lt;as&gt;;
+        route &lt;prefix&gt;;
+        route &lt;prefix&gt; {
+                min count &lt;num&gt;;
+                filter &lt;name&gt;;
+                min filter count &lt;num&gt;;
+                };
+}
+</code>
+<p><descrip>
+        <tag><label id="agg-type">type static|bgp [ as <m/number/ ]</tag>
+         This defines protocol route base attributes to use in summary routes.
+         Protocol router id is used as BGP router id.
+
+        <tag><label id="agg-route">route <m/prefix/</tag> Announce given prefix if any
+        of more specific routes exists. Additionally, you can specify
+        <cf/min count/ value to set minimum number of routes necessary to
+        announce summary. Another option that can be used is <cf/filter name/.
+        One can specify filter (and <cf/min filter count/) to make announce
+        depend on other routes matching filter.
+</descrip>
+
+<sect1>Example
+<label id="agg-exam">
+
+<p>Example configuration looks like this:
+
+<p><code>
+protocol aggregator {
+        type bgp as 65000;
+        route 198.51.100.0/24;
+        route 192.168.0.0/23 { min count 1; filter f_agg; };
+        route 192.168.128.0/23 { filter f_agg2; min filter count 2; };
+}
+</code>
+
+
 <sect>BGP
 <label id="bgp">
 
diff --git a/nest/proto.c b/nest/proto.c
index 1091b32..dd14b5b 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -923,6 +923,9 @@ protos_build(void)
 #ifdef CONFIG_BABEL
   proto_build(&proto_babel);
 #endif
+#ifdef CONFIG_AGG
+  proto_build(&proto_agg);
+#endif
 
   proto_pool = rp_new(&root_pool, "Protocols");
   proto_flush_event = ev_new(proto_pool);
diff --git a/nest/protocol.h b/nest/protocol.h
index ec78735..893841f 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -76,7 +76,8 @@ void protos_dump_all(void);
 
 extern struct protocol
   proto_device, proto_radv, proto_rip, proto_static,
-  proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel;
+  proto_ospf, proto_pipe, proto_bgp, proto_bfd, proto_babel,
+  proto_agg;
 
 /*
  *	Routing Protocol Instance
diff --git a/nest/route.h b/nest/route.h
index 383f4de..a31ef96 100644
--- a/nest/route.h
+++ b/nest/route.h
@@ -561,6 +561,7 @@ extern struct protocol *attr_class_to_protocol[EAP_MAX];
 #define DEF_PREF_RIP		120	/* RIP */
 #define DEF_PREF_BGP		100	/* BGP */
 #define DEF_PREF_PIPE		70	/* Routes piped from other tables */
+#define DEF_PREF_AGG    50  /* Aggregated routes */
 #define DEF_PREF_INHERITED	10	/* Routes inherited from other routing daemons */
 
 
diff --git a/proto/agg/Doc b/proto/agg/Doc
new file mode 100644
index 0000000..c906932
--- /dev/null
+++ b/proto/agg/Doc
@@ -0,0 +1,2 @@
+S agg.c
+S agg_attrs.c
diff --git a/proto/agg/Makefile b/proto/agg/Makefile
new file mode 100644
index 0000000..a689681
--- /dev/null
+++ b/proto/agg/Makefile
@@ -0,0 +1,5 @@
+source=agg.c agg_attrs.c
+root-rel=../../
+dir-name=proto/agg
+
+include ../../Rules
diff --git a/proto/agg/agg.c b/proto/agg/agg.c
new file mode 100644
index 0000000..e19be39
--- /dev/null
+++ b/proto/agg/agg.c
@@ -0,0 +1,718 @@
+/*
+ *	BIRD -- Generic route aggregation
+ *
+ *	(c) 2012-2017 Yandex LLC
+ *	(c) 2012-2017 Alexander V. Chernikov <melifaro@yandex-team.ru>
+ *
+ *	Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Route aggregation
+ *
+ * Aggregation protocol provides general protocol-independent api for
+ * summarizing routes based on config-file defined criteria.
+ */
+
+#undef LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/iface.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "conf/conf.h"
+#include "nest/cli.h"
+#include "filter/filter.h"
+#include "lib/string.h"
+#include "lib/alloca.h"
+
+#include "proto/agg/agg.h"
+
+static void agg_init_sumroute(struct agg_proto *p, struct agg_sumroute_cfg *ac);
+static void agg_announce_sumroute(struct agg_proto *p, struct agg_sumroute *asr);
+static void agg_withdraw_sumroute(struct agg_proto *p, struct agg_sumroute *asr);
+static void agg_consider_announce(struct agg_proto *p, struct agg_sumroute *asr, int aupd);
+
+static int
+agg_import_control(struct proto *P, rte **ee, ea_list **ea UNUSED, struct linpool *p UNUSED)
+{
+  struct proto *pp = (*ee)->sender->proto;
+
+  if (pp == P)
+    return -1;	/* Avoid local loops automatically */
+  return 0;
+}
+
+static int
+agg_reload_routes(struct proto *P)
+{
+  return 1;
+}
+
+/**
+ * trie_match_longest_prefix - find longest prefix match
+ * @t: trie
+ * @px: prefix address
+ * @plen: prefix length
+ *
+ * Tries to find a matching prefix pattern in the trie such that
+ * prefix @px/@plen matches that prefix pattern. Returns prefix pointer
+ * or NULL.
+ */
+static void *
+trie_match_longest_prefix(struct f_trie *t, ip_addr px, int plen)
+{
+  ip_addr pmask = ipa_mkmask(plen);
+  ip_addr paddr = ipa_and(px, pmask);
+  ip_addr cmask;
+  struct f_trie_node *n = t->root, *parent = NULL;
+  struct agg_sumroute_cfg *ac;
+
+  /* Return root node for 0/0 or :: */
+  if (plen == 0)
+    return t->zero ? n : NULL;
+
+  /* Skip root node since it is cath-all node */
+  n = n->c[(ipa_getbit(paddr, 0)) ? 1 : 0];
+
+  while (n)
+    {
+      cmask = ipa_and(n->mask, pmask);
+
+      /* We are out of path */
+      if (ipa_compare(ipa_and(paddr, cmask), ipa_and(n->addr, cmask)))
+	break;
+
+      /* Mask is too specific */
+      if (n->plen > plen)
+	break;
+
+      /* Do not save pointer to branching nodes */
+      ac = (struct agg_sumroute_cfg *)n;
+      if (AGG_VALID_NODE(ac))
+	parent = n;
+
+      /* Choose children */
+      n = n->c[(ipa_getbit(paddr, n->plen)) ? 1 : 0];
+    }
+
+  /*
+   * parent is either
+   * 1) NULL (if the first non-null node does not exist oris out of path)
+   * or
+   * 2) points to the last entry that match
+   *
+   * In former case we check if catch-all prefix really exists and return
+   * either pointer to root node or NULL. In latter case we simply return parent.
+   */
+
+  return parent ? parent : (t->zero ? t->root : NULL);
+}
+
+static void
+trie_walk_call(struct f_trie_node *n, void *func, void *data)
+{
+  void (*f)(struct f_trie_node *, void *) = func;
+  struct agg_sumroute_cfg *ac = (struct agg_sumroute_cfg *)n;
+
+  if (AGG_VALID_NODE(ac))
+    f(n, data);
+
+  if (n->c[0])
+    trie_walk_call(n->c[0], func, data);
+
+  if (n->c[1])
+    trie_walk_call(n->c[1], func, data);
+}
+
+static void
+trie_walk(struct f_trie *t, void *func, void *data)
+{
+  void (*f)(struct f_trie_node *, void *) = func;
+  struct f_trie_node *root;
+
+  root = t->root;
+
+  if (t->zero)
+    f(root, data);
+
+  if (root->c[0])
+    trie_walk_call(root->c[0], func, data);
+  if (t->root->c[1])
+    trie_walk_call(root->c[1], func, data);
+}
+
+
+/*
+ * Trie callback function.
+ * Init newly-created summary routes.
+ */
+static void
+agg_walk_sumroutes_initial(struct f_trie_node *n, void *data)
+{
+  struct agg_sumroute_cfg *ac = (struct agg_sumroute_cfg *)n;
+  struct agg_proto *p = (struct agg_proto *)data;
+
+  agg_init_sumroute(p, ac);
+}
+
+/*
+ * Trie callback function.
+ * Mark given summary route as deleted
+ */
+static void
+agg_mark_sumroute(struct f_trie_node *n, void *data UNUSED)
+{
+  struct agg_sumroute_cfg *ac = (struct agg_sumroute_cfg *)n;
+
+  if (ac->asr)
+    ac->asr->deleted = 1;
+}
+
+/*
+ * Trie callback function.
+ * Remove non-config data associated with summary route
+ */
+static void
+agg_clear_sumroute(struct f_trie_node *tn, void *P)
+{
+  struct agg_proto *p = (struct agg_proto *)P;
+  struct agg_sumroute_cfg *ac_n, *ac;
+  struct agg_sumroute *asr;
+
+  ac = (struct agg_sumroute_cfg *)tn;
+  ac_n = NULL;
+
+  asr = ac->asr;
+  if (!asr || !asr->deleted)
+    return;
+
+  ADBG("Removing summary %I/%d", ac->tn.addr, ac->tn.plen);
+
+  agg_withdraw_sumroute(p, asr);
+
+  /*
+   * Check if we have some nested aggregation routes so
+   * we may need to refeed.
+   */
+  if ((!p->going_down) && (ac->tn.plen))
+    ac_n = trie_match_longest_prefix(p->summary_trie, ac->tn.addr, ac->tn.plen - 1);
+  if (ac_n)
+    p->need_refeed = 1;
+
+  /* Free all resources */
+  fib_free(&asr->route_fib);
+  if (asr->af)
+  {
+    fib_free(&asr->af->filter_fib);
+    rem_node(&asr->af->n);
+    mb_free(asr->af);
+  }
+  mb_free(asr);
+}
+
+/*
+ * Trie callback function.
+ * Inherit given summary route from template config
+ */
+static void
+agg_inherit_sumroute(struct f_trie_node *n, void *data)
+{
+  struct f_trie_node *n_dest;
+  struct agg_sumroute_cfg *ac_src = (struct agg_sumroute_cfg *)n;
+  struct agg_config *cf = (struct agg_config *)data;
+
+  n_dest = (struct f_trie_node *)trie_add_prefix(cf->summary_trie,
+    n->addr, n->plen, n->plen ? n->plen + 1 : 0, ac_src->ac_maxplen);
+
+  /* Copy all route data including filters */
+  memcpy(n_dest + 1, n + 1, sizeof(struct agg_sumroute_cfg) - sizeof(*n));
+}
+
+static void
+agg_initroutefib(struct fib_node *fn)
+{
+
+  /* Zero all additional data fields */
+  memset(fn + 1, 0, sizeof(struct agg_childroute) - sizeof(struct fib_node));
+}
+
+/*
+ * Initialize newly-allocated summary route. Add filter to the list
+ * of protocol filters/
+ */
+static void
+agg_init_sumroute(struct agg_proto *p, struct agg_sumroute_cfg *ac)
+{
+  struct agg_sumroute *asr;
+  struct agg_filter *af;
+
+  ADBG("New summary route %I/%d", ac->tn.addr, ac->tn.plen);
+
+  asr = (struct agg_sumroute *)mb_allocz(p->p.pool, sizeof(*asr));
+
+  /* Init generic fib for matching routes */
+  fib_init(&asr->route_fib, p->p.pool, sizeof(struct agg_childroute), 0,
+    agg_initroutefib);
+  asr->ac = ac;
+  ac->asr = asr;
+
+  /* Check if we have filter for given summary route */
+  if (ac->af)
+    {
+      af = mb_allocz(p->p.pool, sizeof(*af));
+      af->filter = ac->af;
+      fib_init(&af->filter_fib, p->p.pool, sizeof(struct agg_childroute), 0,
+	agg_initroutefib);
+      af->asr = asr;
+      asr->af = af;
+      /* Link structure to per-protocol list */
+      add_tail(&p->filter_list, &af->n);
+    }
+
+  /* Indicate we need refeeed to populate this route */
+  p->need_refeed = 1;
+}
+
+/*
+ * Trie callback function.
+ * Reconfigures summary route
+ */
+static void
+agg_reconfig_sumroute(struct f_trie_node *tn, void *P)
+{
+  struct agg_proto *p = (struct agg_proto *)P;
+  struct agg_sumroute_cfg *ac_o, *ac = (struct agg_sumroute_cfg *)tn;
+  struct agg_sumroute *asr;
+
+  /* Find old corresponding route */
+  ac_o = trie_match_longest_prefix(p->summary_trie, ac->tn.addr, ac->tn.plen);
+
+  if ((!ac_o) || (!ipa_equal(ac_o->tn.addr, ac->tn.addr)) || (ac_o->tn.plen != ac->tn.plen))
+    {
+      /*
+       * Old route is either not found (no candidate, different prefix) or has different type.
+       * Ignore and create new summary.
+       */
+      agg_init_sumroute(p, ac);
+      return;      
+    }
+
+  /*
+   * Route found. Let's check if generic and protocol-dependent data has changed:
+   */
+  if ((ac->ac_flags != ac_o->ac_flags))
+    {
+      /* Reinit route due to changed config flags */
+      agg_init_sumroute(p, ac);
+      return;
+    }
+
+  /* Check if filter was changed */
+  if (!filter_same(ac->af, ac_o->af))
+    {
+      agg_init_sumroute(p, ac);
+      return;
+    }
+
+  ADBG("Reconfiguring summary route %I/%d", ac->tn.addr, ac->tn.plen);
+
+  /* Update pointers */
+  asr = ac_o->asr;
+  asr->deleted = 0;
+  ac->asr = asr;
+  asr->ac = ac;
+  if (ac->af)
+    asr->af->filter = ac->af;
+
+  /* Check if announcement conditions were changed */
+  agg_consider_announce(p, asr, 0);
+}
+
+static int
+agg_reconfigure(struct proto *P, struct proto_config *new)
+{
+  struct agg_config *o = (struct agg_config *)P->cf;
+  struct agg_config *n = (struct agg_config *)new;
+  struct agg_proto *p = (struct agg_proto *)P;
+
+  ADBG("Reconfiguring..");
+
+  if (o->route_src != n->route_src)
+    return 0;
+
+  if (p->cb->check_config(o, n) == 0)
+    return 0;
+
+  /* Mark all old summary routes as deleted */
+  trie_walk(o->summary_trie, agg_mark_sumroute, NULL);
+
+  /* Walk new trie */
+  trie_walk(n->summary_trie, agg_reconfig_sumroute, p);
+
+  /*
+   * agg_reconfig_sumroute() needs old pointer to find
+   * old summary route corresponding to new. On the other
+   * side, agg_clear_sumroute needs new one to request refeed
+   * in case of nested summary routes.
+   */
+  p->summary_trie = n->summary_trie;
+
+  /* Cleanup all old summary routes */
+  trie_walk(o->summary_trie, agg_clear_sumroute, p);
+
+  /* Request feeding if some new summary routes appeared */
+  if (p->need_refeed)
+    {
+      ADBG("Refeeding due to new summary routes configured");
+      proto_request_feeding(P);
+      p->need_refeed = 0;
+    }
+
+  return 1;
+}
+
+static void
+agg_announce_sumroute(struct agg_proto *p, struct agg_sumroute *asr)
+{
+  rte *rte;
+
+  p->cb->update_sumattr(p, asr);
+  if (!asr->attrs)
+    {
+      ADBG("Failed to get summary attribute");
+      return;
+    }
+  
+  ADBG("INSTALL %I/%d", asr->ac->tn.addr, asr->ac->tn.plen);
+  rte = rte_get_temp(rta_clone(asr->attrs));
+  rte->net = net_get(p->p.table, asr->ac->tn.addr, asr->ac->tn.plen);
+  rte->pflags = 0;
+  rte_update(&p->p, rte->net, rte);
+  asr->installed = 1;
+}
+
+static void
+agg_withdraw_sumroute(struct agg_proto *p, struct agg_sumroute *asr)
+{
+  net *net;
+  
+  ADBG("WITHDRAW %I/%d", asr->ac->tn.addr, asr->ac->tn.plen);
+  net = net_get(p->p.table, asr->ac->tn.addr, asr->ac->tn.plen);
+  rte_update(&p->p, net, NULL);
+  asr->installed = 0;
+}
+
+static void
+agg_consider_announce(struct agg_proto *p, struct agg_sumroute *asr, int update)
+{
+  struct agg_sumroute_cfg *ac;
+  int no_announce = 0;
+
+  ac = asr->ac;
+
+  ADBG("Inspecting summary route %I/%d", ac->tn.addr, ac->tn.plen);
+
+  /*
+   * Do not announce summary if there are no more-specific routes
+   * AND filter is not set.
+   */
+  if (!asr->route_fib.entries && !ac->af)
+    {
+      ADBG("Skipping due to lack of entries");
+      no_announce++;
+    }
+
+  /* Minimum value is set, but not reached */
+  if (ac->routes_required && ac->routes_required > asr->route_fib.entries)
+    {
+      ADBG("Skipping due to required limit (%d/%d)", asr->route_fib.entries,
+	ac->routes_required);
+      no_announce++;
+    }
+
+  /* Do not announce if there are no matching filter routes */
+  if (ac->af && !asr->af->filter_fib.entries)
+    {
+      ADBG("Skipping due to lack of filter entries");
+      no_announce++;
+    }
+
+  /* Minimum number of matched filter routes is set, but not reached */
+  if (ac->filter_required && ac->filter_required > asr->af->filter_fib.entries)
+    {
+      ADBG("Skipping due to required number of filter entries not met");
+      no_announce++;
+    }
+
+  /* Either not installed or attribute change */
+  if (!no_announce && (!asr->installed || update))
+      agg_announce_sumroute(p, asr);
+  else if (no_announce && asr->installed)
+      agg_withdraw_sumroute(p, asr);
+}
+
+static void
+agg_rt_notify(struct proto *P, rtable *src_table, net *n, rte *new, rte *old, ea_list *attrs)
+{
+  struct agg_proto *p = (struct agg_proto *) P;
+  struct agg_sumroute_cfg *ac;
+  struct agg_sumroute *asr = NULL;
+  struct agg_filter *af;
+  struct agg_childroute *ar = NULL;
+  ea_list *tmpa;
+  char *action;
+  u32 cattrs = 0;
+  int old_ok, new_ok;
+  int aupd, v;
+
+  /* Ignore unreachable routes */
+  if ((new) && (new->attrs->dest == RTD_UNREACHABLE))
+    new = NULL;
+
+  if ((old) && (old->attrs->dest == RTD_UNREACHABLE))
+    old = NULL;
+
+  if (!new && !old)
+    return;
+
+  action = "+-";
+  if (new && !old)
+	  action = "++";
+  if (old && !new)
+	  action = "--";
+  ADBG("Got %s %I/%d route", action, n->n.prefix, n->n.pxlen);
+
+  /* 
+   * Search trie to determine summary route.
+   * We use 1 bit less specific prefix to deal with the following 2 cases:
+   * 1) if announced X/Y prefix is the same as summary route this is clearly not the case for summarization
+   * 2) if nested summary routes are configured and 1) is in action we got wrong asr pointer.
+   *
+   * We skip 0/0 and :: due to it can'be summarized.
+   * We also assume trie_match() to normalize address with network mask
+   */
+  if ((n->n.pxlen) && ((ac = trie_match_longest_prefix(p->summary_trie, n->n.prefix, n->n.pxlen - 1))))
+    {
+	ADBG("Found matched summary route %I/%d", ac->tn.addr, ac->tn.plen);
+	asr = ac->asr;
+	aupd = 0;
+
+	if (new)
+	  {
+	    /* new or updated route */
+	    cattrs = p->cb->compact_sumattr(new);
+	    ar = fib_get(&asr->route_fib, &n->n.prefix, n->n.pxlen);
+	    aupd = p->cb->update_route(p, asr, ar->attrs, cattrs);
+	    /* Sanity checks */
+	    if ((!old && ar->attrs) || (old && !ar->attrs))
+	      {
+		ADBG("Prefix %I/%d old_addr=%X newattw=%X old=%p", n->n.prefix, n->n.pxlen,
+		  ar->attrs, cattrs, old);
+	      }
+	    ar->attrs = cattrs;
+	  }
+	else if (old)
+	  {
+	    ar = fib_find(&asr->route_fib, &n->n.prefix, n->n.pxlen);
+	    if (ar)
+	      {
+	    	aupd = p->cb->update_route(p, asr, ar->attrs, 0);
+		fib_delete(&asr->route_fib, ar);
+	      }
+	    else
+	      {
+		ADBG("Prefix %I/%d not found", n->n.prefix, n->n.pxlen);
+	      }
+	  }
+
+	agg_consider_announce(p, asr, aupd);
+    }
+      
+  /* Check filters at last */
+  WALK_LIST(af, p->filter_list)
+    {
+      old_ok = 0;
+      new_ok = 0;
+
+      ADBG("Checking filter %s for %I/%d (asr %I/%d)",
+	af->filter->name, n->n.prefix, n->n.pxlen,
+	af->asr->ac->tn.addr, af->asr->ac->tn.plen);
+      if (old)
+	{
+	  /* */
+  	  tmpa = NULL;
+	  v = f_run(af->filter, &old, &tmpa, p->lp, FF_FORCE_TMPATTR);
+	  old_ok = (v <= F_ACCEPT) ? 1 : 0;
+	}
+      if (new)
+	{
+	  /* */
+  	  tmpa = NULL;
+	  v = f_run(af->filter, &new, &tmpa, p->lp, FF_FORCE_TMPATTR);
+	  new_ok = (v <= F_ACCEPT) ? 1 : 0;
+	}
+      ADBG("Result: old: %d new: %d", old_ok, new_ok);
+      if (old_ok ==  new_ok)
+	{
+	  /* no match or update */
+	  continue;
+	}
+
+      /* New or withdraw */
+      if (old_ok)
+	{
+	  ar = fib_find(&af->filter_fib, &n->n.prefix, n->n.pxlen);
+	  if (ar)
+	      fib_delete(&af->filter_fib, ar);
+	}
+      if (new_ok)
+	  ar = fib_get(&af->filter_fib, &n->n.prefix, n->n.pxlen);
+
+      /* Recalc announce state */
+      agg_consider_announce(p, af->asr, 0);
+    }
+
+  lp_flush(p->lp);
+}
+
+
+static struct proto *
+agg_init(struct proto_config *C)
+{
+  struct proto *P = proto_new(C, sizeof(struct agg_proto));
+
+  P->accept_ra_types = RA_OPTIMAL;
+  P->reload_routes = agg_reload_routes;
+  P->import_control = agg_import_control;
+  P->rt_notify = agg_rt_notify;
+
+  return P;
+}
+
+static int
+agg_start(struct proto *P)
+{
+  struct agg_proto *p = (struct agg_proto *)P;
+  struct agg_config *cf = (struct agg_config *)P->cf;
+
+  switch (cf->route_src)
+    {
+    case RTS_BGP:
+      p->cb = &bgp_cb;
+      break;
+    case RTS_STATIC:
+      p->cb = &static_cb;
+      break;
+    default:
+      return PS_DOWN;
+    }
+
+  p->going_down = 0;
+  init_list(&p->filter_list);
+  /* Allocate by 16k blocks (while BGP requests 1k block) */
+  p->lp = lp_new(P->pool, 16384 - 16);
+
+  /* Import configuration */
+  p->summary_trie = cf->summary_trie;
+  trie_walk(p->summary_trie, agg_walk_sumroutes_initial, p);
+  /* Ignore refeed request */
+  p->need_refeed = 0;
+
+  return PS_UP;
+}
+
+static int
+agg_shutdown(struct proto *P)
+{
+  struct agg_proto *p = (struct agg_proto *)P;
+
+  /* Indicate we're not reconfiguring */
+  p->going_down = 1;
+
+  /* Mark all summary routes as deleted */
+  trie_walk(p->summary_trie, agg_mark_sumroute, NULL);
+
+  /* Cleanup marked (all) summary routes */
+  trie_walk(p->summary_trie, agg_clear_sumroute, p);
+
+  /* Flush all contents */
+  lp_flush(p->lp);
+
+  return PS_DOWN;
+}
+
+static void
+agg_copy_config(struct proto_config *dest, struct proto_config *src)
+{
+  struct agg_config *cf_dest = (struct agg_config *)dest;
+  struct agg_config *cf_src = (struct agg_config *)src;
+  struct f_trie *summary_trie;
+
+  /* Save pointers we need to keep */
+  summary_trie = cf_dest->summary_trie;
+
+  /* Copy all fields by default */
+  proto_copy_rest(dest, src, sizeof(struct agg_config));
+
+  /* Copy trie to new config */
+  cf_dest->summary_trie = summary_trie;
+  trie_walk(cf_src->summary_trie, agg_inherit_sumroute, cf_dest);
+}
+
+struct protocol proto_agg = {
+  .name =		"AGG",
+  .template =		"agg%d",
+  .preference =		DEF_PREF_AGG,
+  .config_size =	sizeof(struct agg_config),
+  .init =		agg_init,
+  .start =		agg_start,
+  .reconfigure =	agg_reconfigure,
+  .copy_config = 	agg_copy_config,
+  .shutdown =		agg_shutdown,
+};
+
+static void
+agg_show_asr(struct f_trie_node *n, void *data)
+{
+  struct agg_sumroute_cfg *ac = (struct agg_sumroute_cfg *)n;
+  struct agg_sumroute *asr;
+  struct agg_filter *af;
+  struct agg_childroute *ar;
+
+  asr = ac->asr;
+  af = asr ? asr->af : NULL;
+
+  cli_msg(-1009, "%I/%d %d/%d %s", ac->tn.addr, ac->tn.plen,
+    asr->route_fib.entries, ac->routes_required,
+    (asr && asr->installed) ? " (installed)" : "");
+
+  if (asr && asr->route_fib.entries)
+    {
+      FIB_WALK(&asr->route_fib, fn) {
+	ar = (struct agg_childroute *)fn;
+	cli_msg(-1009, "\t%I/%d [%X]", ar->fn.prefix, ar->fn.pxlen,
+	  ar->attrs & AGG_RTE_MASK);
+      } FIB_WALK_END;
+    }
+
+  if (af && af->filter_fib.entries)
+    {
+      cli_msg(-1009, " Filter: %s %d/%d", af->filter->name,
+	af->filter_fib.entries, ac->filter_required);
+      FIB_WALK(&af->filter_fib, fn) {
+	ar = (struct agg_childroute *)fn;
+	cli_msg(-1009, "\t%I/%d", ar->fn.prefix, ar->fn.pxlen);
+      } FIB_WALK_END;
+    }
+}
+
+void
+agg_show(struct proto *P)
+{
+  struct agg_proto *p = (struct agg_proto *)P;
+
+  trie_walk(p->summary_trie, agg_show_asr, p);
+  cli_msg(0, "");
+}
+
diff --git a/proto/agg/agg.h b/proto/agg/agg.h
new file mode 100644
index 0000000..5a56194
--- /dev/null
+++ b/proto/agg/agg.h
@@ -0,0 +1,109 @@
+/*
+ *	BIRD -- Generic route aggregation
+ *
+ *	(c) 2012-2015 Yandex LLC
+ *	(c) 2012-2015 Alexander V. Chernikov <melifaro@yandex-team.ru>
+ *
+ *	Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+#ifndef _BIRD_RT_AGG_H_
+#define _BIRD_RT_AGG_H_
+
+#define ADBG(msg, ...)	DBG("%s:%d " msg "\n", __FUNCTION__, __LINE__, ##__VA_ARGS__)
+
+struct agg_cb;
+
+struct agg_proto {
+  struct proto p;
+  struct f_trie *summary_trie;		/* Trie with summary routes */
+  struct linpool *lp;			/* Linear pool used by aggregation functions */
+  int need_refeed;			/* Set if refeed is required */
+  int going_down;			/* Set if shutdown is requested */
+  list filter_list;			/* List of agg route filters */
+  struct agg_cb *cb;			/* Pointer to protocol-specific callbacks */
+};
+
+struct agg_config {
+  struct proto_config c;
+  struct f_trie *summary_trie;		/* Trie for holding summary routes */
+  int route_src;			/* route protocol (RTS_*)  */
+  union {				/* protocol-specific data */
+    struct {
+      u32 local_as;			/* BGP local ASn */
+    } bgp;
+  } u;
+};
+
+extern struct protocol proto_agg;
+
+struct agg_filter {
+  node n;				/* Member of filter list to check */
+  struct fib filter_fib;		/* Routes matching fib */
+  struct filter *filter;		/* Filter set */
+  struct agg_sumroute *asr;		/* Pointer to matching summary route */
+  u32 sum_filter;			/* Number of routes matching filter */
+};
+
+struct agg_sumroute_cfg {
+  struct f_trie_node tn;		/* Information about network */
+  struct agg_sumroute *asr;		/* Pointer to runtime data */
+  					/* -- the rest fields can be copied -- */
+  struct filter *af;			/* Custom filter if set */
+  u32 routes_required;			/* Minumum number of matching routes */
+  u32 filter_required;			/* Minumum number of filter-matching routes */
+  u16 ac_flags;				/* Aggregation flags */
+  u16 ac_maxplen;			/* Max prefix length: AF */
+};
+
+/* Aggregated route information */
+struct agg_sumroute {
+  struct fib route_fib;			/* Summary routes */
+  union {
+    struct {
+      u32 origin_inc_count;		/* Number of routes with INCOMPLETE origin */
+      u32 origin_egp_count;		/* Number of routes with IGP origin */
+      u32 origin_igp_count;		/* Number of routes with EGP origin */
+      u32 atomic_agg_count;		/* Number of routes with ATOMIC_AGG */
+    } bgp;
+  } u;
+  struct agg_sumroute_cfg *ac;
+  struct rta *attrs;			/* Aggregated route attributes */
+  struct agg_filter *af;		/* Custom filter if set */
+  int installed;
+  int deleted;
+};
+
+struct agg_childroute {
+  struct fib_node fn;			/* Network node (both) */
+  u32 attrs;				/* Compressed attributes */
+};
+
+void agg_show(struct proto *P);
+
+#define AGG_FLAG_PREPARED	0x0001	/* Entry is set up (used in trie checking) */
+#define	AGG_VALID_NODE(x)	((x)->ac_flags & AGG_FLAG_PREPARED)	/* Protect from branching nodes */
+
+struct agg_proto;
+struct agg_config;
+struct agg_sumroute;
+
+#define	AGG_EXISTS_RTE	0xFF000000
+#define	AGG_RTE_MASK	0x00FFFFFF
+
+typedef u32 (*compact_sumattr_f)(struct rte *);
+typedef int (*update_sumattr_f)(struct agg_proto *, struct agg_sumroute *);
+typedef int (*update_route_f)(struct agg_proto *, struct agg_sumroute *,
+    u32, u32);
+typedef int (*check_config_f)(struct agg_config *, struct agg_config *);
+
+struct agg_cb {
+  compact_sumattr_f compact_sumattr;	/* Save necessary attribute values in compact form */
+  update_route_f update_route;		/* Update rte attributes */
+  update_sumattr_f update_sumattr;	/* Update summary attribute */
+  check_config_f check_config;		/* Check agg config parameters */
+};
+
+extern struct agg_cb bgp_cb, static_cb;
+
+#endif
diff --git a/proto/agg/agg_attrs.c b/proto/agg/agg_attrs.c
new file mode 100644
index 0000000..7c7a633
--- /dev/null
+++ b/proto/agg/agg_attrs.c
@@ -0,0 +1,375 @@
+/*
+ *	BIRD -- Generic route aggregation
+ *
+ *	(c) 2015-2017 Yandex LLC
+ *	(c) 2015-2017 Alexander V. Chernikov <melifaro@yandex-team.ru>
+ *
+ *	Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+/**
+ * DOC: Route aggregation
+ *
+ * Protocol-specific callbacks to compact/create route attributes
+ * for child/summary routes.
+ */
+
+#undef	LOCAL_DEBUG
+
+#include "nest/bird.h"
+#include "nest/protocol.h"
+#include "nest/route.h"
+#include "filter/filter.h"
+#include "lib/string.h"
+#include "lib/alloca.h"
+
+#include "proto/agg/agg.h"
+#include "proto/bgp/bgp.h"
+
+#define	AGG_BGP_GET_ORIGIN(x)	(((x) & 0xFF00) >> 8)
+#define	AGG_BGP_SET_ORIGIN(x)	((x) << 8)
+#define	AGG_BGP_SET_AGGR(x)	(x)
+#define	AGG_BGP_GET_AGGR(x)	((x) & 0x01)
+
+/*
+ * bgp_sum_compact_rta - save relevant attributes
+ * @rta: pointer to route attributes
+ *
+ * Function retrieves/determines ORIGIN and ATOMIC_AGG
+ * attribute values. It then encodes them in u32 variable
+ * and returns it. Note that we have to return non-zero
+ * value to help encoding 'no rte' case/
+ */
+static u32
+bgp_sum_compact_attr(struct rte *rte)
+{
+  u8 aagg, origin;
+  u32 res;
+  struct eattr *ea;
+  rta *attrs = rte->attrs;
+
+  if (ea = ea_find(attrs->eattrs, EA_CODE(EAP_BGP, BA_ORIGIN)))
+    origin = ea->u.data;
+  else
+    {
+      switch (attrs->source)
+	{
+	case RTS_OSPF:
+	case RTS_OSPF_IA:
+	case RTS_OSPF_EXT1:
+	case RTS_OSPF_EXT2:
+	  origin = ORIGIN_IGP;
+	  break;
+	default:
+	  origin = ORIGIN_INCOMPLETE;
+	}
+    }
+
+  aagg = ea_find(attrs->eattrs, EA_CODE(EAP_BGP, BA_ATOMIC_AGGR)) ? 1 : 0;
+
+  res = AGG_EXISTS_RTE | AGG_BGP_SET_ORIGIN(origin) | AGG_BGP_SET_AGGR(aagg);
+  return res;
+}
+
+/*
+ * bgp_sum_locate_origin - returns pointer to per-asr
+ * origin attribute stats for given ORIGIN value.
+ * @asr: pointer to summary route
+ * @ca: compressed route attribute
+ *
+ * Returns pointer to requested stat value.
+ */
+inline static u32 *
+bgp_sum_locate_origin(struct agg_sumroute *asr, u32 ca)
+{
+  int origin = AGG_BGP_GET_ORIGIN(ca);
+
+  switch (origin)
+    {
+    case ORIGIN_IGP:
+      return &asr->u.bgp.origin_igp_count;
+    case ORIGIN_EGP:
+      return &asr->u.bgp.origin_egp_count;
+    default:
+      return &asr->u.bgp.origin_inc_count;
+    }
+}
+
+/*
+ * bgp_sum_create_rta - create summary route attribute
+ * @p: pointer to protocol instance
+ * @asr: pointer to summary route
+ * @origin: value of ORIGIN attribute
+ * @atomic_agg: value of ATOMIC_AGGREGATE attribute
+ *
+ * Function creates stable rta (via rta_clone) and announces it
+ */
+static rta *
+bgp_sum_create_rta(struct agg_proto *p, struct agg_sumroute_cfg *ac, int origin, int atomic_agg)
+{
+  int i, slen;
+  struct ea_list *eal;
+  struct agg_config *cf = (struct agg_config *)p->p.cf;
+  rta a, *attrs;
+  byte *data;
+
+  slen = atomic_agg ? 4 : 3;
+  eal = lp_allocz(p->lp, sizeof(struct ea_list) + sizeof(eattr) * slen);
+  eal->flags = EALF_SORTED;
+  eal->count = slen;
+
+  i = 0;
+
+  ADBG("called for %I/%d", ac->tn.addr, ac->tn.plen);
+
+  /*
+   * Do route aggregation per RFC4271 9.2.2.2 rules
+   *
+   * [0] ORIGIN (internal, u32)
+   * [1] AS_PATH (empty)
+   * [2] ATOMIC_AGGREGATE (opt, empty)
+   * [3] AGGREGATOR (8 bytes)
+   */
+
+  /* ORIGIN */
+  bgp_set_attr(&eal->attrs[i++], BA_ORIGIN, origin);
+
+  /* AS_PATH */
+  bgp_set_attr_wa(&eal->attrs[i++], p->lp, BA_AS_PATH, 0);
+
+  /* ATOMIC_AGGREGATE */
+  if (atomic_agg)
+    bgp_set_attr_wa(&eal->attrs[i++], p->lp, BA_ATOMIC_AGGR, 0);
+
+  /* AGGREGATOR */
+  data = bgp_set_attr_wa(&eal->attrs[i++], p->lp, BA_AGGREGATOR, 8);
+  put_u32(data, cf->u.bgp.local_as);
+  put_u32(data + 4, cf->c.router_id);
+
+  /* Fill in temporary rta */
+  bzero(&a, sizeof(a));
+  a.src = p->p.main_source;
+  a.source = RTS_BGP;
+  a.scope = SCOPE_UNIVERSE;
+  a.cast = RTC_UNICAST;
+  a.dest = RTD_BLACKHOLE;
+
+  a.eattrs = eal;
+  attrs = rta_clone(rta_lookup(&a));
+  lp_flush(p->lp);
+
+  return attrs;
+}
+
+static int
+bgp_sum_recalc_attr(struct agg_proto *p, struct agg_sumroute *asr)
+{
+  struct eattr *ea;
+  rta *a;
+  int new_atomic, new_origin, rebuild = 0;
+
+  a = asr->attrs;
+
+  /* Calculate attribute values per RFC 4271 9.2.2 */
+  /* Resulting ORIGIN value */
+  if (asr->u.bgp.origin_inc_count)
+    new_origin = ORIGIN_INCOMPLETE;
+  else if (asr->u.bgp.origin_egp_count)
+    new_origin = ORIGIN_EGP;
+  else
+    new_origin = ORIGIN_IGP;
+
+  /* Resulting atomic_agg value */
+  new_atomic = asr->u.bgp.atomic_agg_count ? 1 : 0;
+
+  ea = a ? ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_ORIGIN)) : NULL;
+  if (!ea || (ea->u.data != new_origin))
+    {
+      ADBG("Summary route update requires reannounce due to ORIGIN change");
+      rebuild = 1;
+    }
+
+  ea = a ? ea_find(a->eattrs, EA_CODE(EAP_BGP, BA_ATOMIC_AGGR)) : NULL;
+  if ((ea && !new_atomic) || (!ea && new_atomic))
+    {
+      ADBG("Summary route update requires reannounce due to ATOMIC_AGGR change");
+      rebuild = 1;
+    }
+
+  if (!a)
+    {
+      ADBG("Creating summary route attribute");
+      rebuild = 1;
+    }
+
+  if (!rebuild)
+    return 0;
+
+  a = bgp_sum_create_rta(p, asr->ac, new_origin, new_atomic);
+  if (asr->attrs)
+      rta_free(asr->attrs);
+
+  asr->attrs = a;
+  return 1;
+}
+
+/*
+ * Update per-route data for given summary route.
+ * Recalculates summary route atribute.
+ * @p: pointer to protocol instance
+ * @asr: pointer to summary route
+ * @ar: changed route
+ * @old: old attributes
+ * @new: new attributes
+ */
+static int
+bgp_sum_update_route(struct agg_proto *p, struct agg_sumroute *asr, u32 old, u32 new)
+{
+  u32 *p_origin;
+
+  if (old)
+    {
+      /* Decrease stats for old rte at first */
+      p_origin = bgp_sum_locate_origin(asr, old);
+      *p_origin -= 1;
+      if (AGG_BGP_GET_AGGR(old))
+	asr->u.bgp.atomic_agg_count--;
+    }
+
+  if (new)
+    {
+      /* Then put them back if needed */
+      p_origin = bgp_sum_locate_origin(asr, new);
+      *p_origin += 1;
+      if (AGG_BGP_GET_AGGR(new))
+	asr->u.bgp.atomic_agg_count++;
+    }
+
+  return bgp_sum_recalc_attr(p, asr);
+}
+
+
+/*
+ * bgp_check_agg_config - checks if protocol specific parameters are the same
+ * @p: pointer to protocol instance
+ * @asr_o: old summary route
+ * @asr: new summary route
+ *
+ * Returns 1 if parameters are the same, 0 otherwise.
+ */
+static int
+bgp_check_agg_config(struct agg_config *oc, struct agg_config *nc)
+{
+  if (oc->u.bgp.local_as != nc->u.bgp.local_as)
+    return 0;
+
+  return 1;
+}
+
+struct agg_cb bgp_cb = {
+	.compact_sumattr =	bgp_sum_compact_attr,
+	.update_route =		bgp_sum_update_route,
+	.update_sumattr =	bgp_sum_recalc_attr,
+	.check_config =		bgp_check_agg_config,
+};
+
+/*
+ * static_sum_compact_rta - save necessary rta attrs
+ * @rta: pointer to route attributes
+ *
+ * Any non-zero value is OK.
+ */
+static u32
+static_sum_compact_attr(struct rte *rte)
+{
+
+  return AGG_EXISTS_RTE;
+}
+
+/*
+ * bgp_sum_create_rta - create summary route attribute
+ * @p: pointer to protocol instance
+ * @asr: pointer to summary route
+ * @origin: value of ORIGIN attribute
+ * @atomic_agg: value of ATOMIC_AGGREGATE attribute
+ *
+ * Function creates stable rta (via rta_clone) and announces it
+ */
+static rta *
+static_sum_create_rta(struct agg_proto *p, struct agg_sumroute_cfg *ac)
+{
+  rta a, *attrs;
+
+  ADBG("called for %I/%d", ac->tn.addr, ac->tn.plen);
+
+  /* Fill in temporary rta */
+  bzero(&a, sizeof(a));
+  a.src = p->p.main_source;
+  a.source = RTS_STATIC;
+  a.scope = SCOPE_UNIVERSE;
+  a.cast = RTC_UNICAST;
+  a.dest = RTD_BLACKHOLE;
+  attrs = rta_clone(rta_lookup(&a));
+
+  return attrs;
+}
+
+/*
+ * Update and reannounce summary route
+ * @p: pointer to protocol instance
+ * @asr: pointer to summary route
+ * @ar: changed route
+ * @old: old attributes
+ * @new: new attributes
+ */
+static int
+static_sum_update_attr(struct agg_proto *p, struct agg_sumroute *asr)
+{
+
+  if (asr->attrs)
+    return 0;
+
+  asr->attrs = static_sum_create_rta(p, asr->ac);
+  return 1;
+}
+
+/*
+ * Update and reannounce summary route
+ * @p: pointer to protocol instance
+ * @asr: pointer to summary route
+ * @ar: changed route
+ * @old: old attributes
+ * @new: new attributes
+ */
+static int
+static_sum_update_route(struct agg_proto *p, struct agg_sumroute *asr, u32 old, u32 new)
+{
+
+  if (asr->attrs)
+    return 0;
+
+  return static_sum_update_attr(p, asr);
+}
+
+/*
+ * static_check_sumroute - checks if protocol specific parameters are the same
+ * @p: pointer to protocol instance
+ * @asr_o: old summary route
+ * @asr: new summary route
+ *
+ * Returns 1 if parameters are the same, 0 otherwise.
+ */
+static int
+static_check_agg_config(struct agg_config *oc, struct agg_config *nc)
+{
+
+  return 1;
+}
+
+struct agg_cb static_cb = {
+	.compact_sumattr =	static_sum_compact_attr,
+	.update_route =		static_sum_update_route,
+	.update_sumattr =	static_sum_update_attr,
+	.check_config =		static_check_agg_config,
+};
+
diff --git a/proto/agg/config.Y b/proto/agg/config.Y
new file mode 100644
index 0000000..ea9dbeb
--- /dev/null
+++ b/proto/agg/config.Y
@@ -0,0 +1,95 @@
+/*
+ *	BIRD -- Generic route aggregation
+ *
+ *	(c) 2012-2015 Yandex LLC
+ *	(c) 2012-2015 Alexander V. Chernikov <melifaro@yandex-team.ru>
+ *
+ *	Can be freely distributed and used under the terms of the GNU GPL.
+ */
+
+CF_HDR
+
+#include "proto/agg/agg.h"
+
+CF_DEFINES
+
+#undef LOCAL_DEBUG
+
+#define AGG_CFG ((struct agg_config *) this_proto)
+int current_rtype = 0;
+struct protocol *current_rproto = NULL;
+u32 bgp_as = 0;
+struct agg_sumroute_cfg *ac;
+
+CF_DECLS
+
+CF_KEYWORDS(AGGREGATOR, ROUTE, BGP, AS)
+
+CF_GRAMMAR
+
+CF_ADDTO(proto, agg_proto '}')
+
+agg_proto_start: proto_start AGGREGATOR {
+     this_proto = proto_config_new(&proto_agg, $1);
+     AGG_CFG->summary_trie = f_new_trie(cfg_mem, sizeof(struct agg_sumroute_cfg));
+  }
+ ;
+
+agg_proto:
+   agg_proto_start proto_name '{'
+ | agg_proto agg_proto_item ';'
+ ;
+
+agg_proto_item:
+   proto_item
+  | ROUTE prefix {
+    int plen = $2.len;
+    int maxplen = MAX_PREFIX_LENGTH;
+    /*
+     * Set minimum matched  prefix length to zero for 0/0 and ::
+     * to ensure root node is dispatched by walk_trie()
+     */
+    ac = (struct agg_sumroute_cfg *)trie_add_prefix(AGG_CFG->summary_trie,
+	$2.addr, plen, plen ? plen + 1 : 0, maxplen);
+    if (ac->ac_flags & AGG_FLAG_PREPARED)
+      cf_error("Prefix %I/%d already exists", $2.addr, $2.len);
+
+    ac->ac_flags = AGG_FLAG_PREPARED;	/* Indicate node is not branching */
+    ac->ac_maxplen = maxplen;
+  } agg_options
+  | TYPE BGP AS expr {
+      if (AGG_CFG->route_src != 0 && AGG_CFG->route_src != RTS_BGP)
+	cf_error("Route source was already configured");
+      AGG_CFG->route_src = RTS_BGP;
+      AGG_CFG->u.bgp.local_as = $4;
+   }
+  | TYPE STATIC {
+      if (AGG_CFG->route_src != 0 && AGG_CFG->route_src != RTS_STATIC)
+	cf_error("Route source was already configured");
+      AGG_CFG->route_src = RTS_STATIC;
+   }
+  ;
+
+agg_options:
+  | '{' agg_options_entries '}'
+  ;
+
+agg_options_entries:
+  agg_option ';'
+  | agg_options_entries agg_option ';'
+  ;
+
+agg_option:
+     MIN COUNT expr { ac->routes_required = $3; }
+  |  MIN FILTER COUNT expr { ac->filter_required = $4; }
+  |  FILTER filter { ac->af = $2; }
+  ;
+
+
+CF_CLI(SHOW AGG, optsym, [<name>], [[Show details of aggregator protocol]])
+{ agg_show(proto_get_named($3, &proto_agg)); } ;
+ 
+CF_CODE
+
+CF_END
+
diff --git a/proto/bgp/attrs.c b/proto/bgp/attrs.c
index 9d23374..5674ac9 100644
--- a/proto/bgp/attrs.c
+++ b/proto/bgp/attrs.c
@@ -351,7 +351,7 @@ bgp_alloc_adata(struct linpool *pool, unsigned len)
   return ad;
 }
 
-static void
+void
 bgp_set_attr(eattr *e, unsigned attr, uintptr_t val)
 {
   ASSERT(ATTR_KNOWN(attr));
@@ -364,7 +364,7 @@ bgp_set_attr(eattr *e, unsigned attr, uintptr_t val)
     e->u.ptr = (struct adata *) val;
 }
 
-static byte *
+byte *
 bgp_set_attr_wa(eattr *e, struct linpool *pool, unsigned attr, unsigned len)
 {
   struct adata *ad = bgp_alloc_adata(pool, len);
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index d028bef..ac8fe9c 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -247,6 +247,8 @@ void bgp_attach_attr(struct ea_list **to, struct linpool *pool, unsigned attr, u
 byte *bgp_attach_attr_wa(struct ea_list **to, struct linpool *pool, unsigned attr, unsigned len);
 struct rta *bgp_decode_attrs(struct bgp_conn *conn, byte *a, uint len, struct linpool *pool, int mandatory);
 int bgp_get_attr(struct eattr *e, byte *buf, int buflen);
+void bgp_set_attr(eattr *e, unsigned attr, uintptr_t val);
+byte *bgp_set_attr_wa(eattr *e, struct linpool *pool, unsigned attr, unsigned len);
 int bgp_rte_better(struct rte *, struct rte *);
 int bgp_rte_mergable(rte *pri, rte *sec);
 int bgp_rte_recalculate(rtable *table, net *net, rte *new, rte *old, rte *old_best);
diff --git a/sysdep/autoconf.h.in b/sysdep/autoconf.h.in
index c73270c..1ef03d8 100644
--- a/sysdep/autoconf.h.in
+++ b/sysdep/autoconf.h.in
@@ -44,6 +44,7 @@
 #undef CONFIG_OSPF
 #undef CONFIG_PIPE
 #undef CONFIG_BABEL
+#undef CONFIG_AGG
 
 /* We use multithreading */
 #undef USE_PTHREADS
-- 
2.4.6

