]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Aggregator: Expressed most of the attribute logic in filter language
authorMaria Matejka <mq@ucw.cz>
Tue, 31 Oct 2023 10:37:54 +0000 (11:37 +0100)
committerMaria Matejka <mq@ucw.cz>
Wed, 1 Nov 2023 09:15:16 +0000 (10:15 +0100)
proto/aggregator/aggregator.c
proto/aggregator/aggregator.h
proto/aggregator/config.Y
proto/aggregator/test.conf

index 944d2f0991175b3f29fa1fe22067a99e151f3073..03d7bbf9b8ac7515ec7093be1046b3631fa7e509 100644 (file)
 #include "aggregator.h"
 
 #include <stdlib.h>
-/*
-#include "nest/route.h"
-#include "nest/iface.h"
-#include "lib/resource.h"
-#include "lib/event.h"
-#include "lib/timer.h"
-#include "lib/string.h"
-#include "conf/conf.h"
-#include "filter/filter.h"
-#include "filter/data.h"
-#include "lib/hash.h"
-#include "lib/string.h"
-#include "lib/alloca.h"
-#include "lib/flowspec.h"
-*/
-
-/*
- * Set static attribute in @rta from static attribute in @old according to @sa.
- */
-static void
-rta_set_static_attr(struct rta *rta, const struct rta *old, struct f_static_attr sa)
-{
-  switch (sa.sa_code)
-  {
-    case SA_NET:
-      break;
-
-    case SA_FROM:
-      rta->from = old->from;
-      break;
-
-    case SA_GW:
-      rta->dest = RTD_UNICAST;
-      rta->nh.gw = old->nh.gw;
-      rta->nh.iface = old->nh.iface;
-      rta->nh.next = NULL;
-      rta->hostentry = NULL;
-      rta->nh.labels = 0;
-      break;
-
-    case SA_SCOPE:
-      rta->scope = old->scope;
-      break;
-
-    case SA_DEST:
-      rta->dest = old->dest;
-      rta->nh.gw = IPA_NONE;
-      rta->nh.iface = NULL;
-      rta->nh.next = NULL;
-      rta->hostentry = NULL;
-      rta->nh.labels = 0;
-      break;
-
-    case SA_IFNAME:
-      rta->dest = RTD_UNICAST;
-      rta->nh.gw = IPA_NONE;
-      rta->nh.iface = old->nh.iface;
-      rta->nh.next = NULL;
-      rta->hostentry = NULL;
-      rta->nh.labels = 0;
-      break;
-
-    case SA_GW_MPLS:
-      rta->nh.labels = old->nh.labels;
-      memcpy(&rta->nh.label, &old->nh.label, sizeof(u32) * old->nh.labels);
-      break;
-
-    case SA_WEIGHT:
-      rta->nh.weight = old->nh.weight;
-      break;
-
-    case SA_PREF:
-      rta->pref = old->pref;
-      break;
-
-    default:
-      bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
-  }
-}
 
 /*
  * Compare list of &f_val entries.
@@ -158,35 +79,23 @@ aggregator_bucket_update(struct aggregator_proto *p, struct aggregator_bucket *b
     return;
   }
 
-  /* Allocate RTA and EA list */
+  /* Store TMP linpool state */
+  struct lp_state tmp_state;
+  lp_save(tmp_linpool, &tmp_state);
+
+  /* Allocate RTA */
   struct rta *rta = allocz(rta_size(bucket->rte->attrs));
   rta->dest = RTD_UNREACHABLE;
   rta->source = RTS_AGGREGATED;
   rta->scope = SCOPE_UNIVERSE;
 
-  struct ea_list *eal = allocz(sizeof(struct ea_list) + sizeof(struct eattr) * p->aggr_on_da_count);
-  eal->next = NULL;
-  eal->count = 0;
-  rta->eattrs = eal;
-
-  /* Seed the attributes from aggregator rule */
-  for (uint i = 0; i < p->aggr_on_count; i++)
-  {
-    if (p->aggr_on[i].type == AGGR_ITEM_DYNAMIC_ATTR)
-    {
-      u32 ea_code = p->aggr_on[i].da.ea_code;
-      const struct eattr *e = ea_find(bucket->rte->attrs->eattrs, ea_code);
-
-      if (e)
-        eal->attrs[eal->count++] = *e;
-    }
-    else if (p->aggr_on[i].type == AGGR_ITEM_STATIC_ATTR)
-      rta_set_static_attr(rta, bucket->rte->attrs, p->aggr_on[i].sa);
-  }
-
+  /* Allocate route */
   struct rte *new = rte_get_temp(rta, bucket->rte->src);
   new->net = net;
 
+  /* Seed the attributes from aggregator rule */
+  f_eval_rte(p->premerge, &new, tmp_linpool, p->aggr_on_count, bucket->aggr_data, 0, NULL);
+
   /*
   log("=============== CREATE MERGED ROUTE ===============");
   log("New route created: id = %d, protocol: %s", new->src->global_id, new->src->proto->name);
@@ -199,11 +108,8 @@ aggregator_bucket_update(struct aggregator_proto *p, struct aggregator_bucket *b
     .val.rte = bucket->rte,
   };
 
-  struct lp_state tmp_state;
-  lp_save(tmp_linpool, &tmp_state);
-
-  /* Actually run the filter */
-  enum filter_return fret = f_eval_rte(p->merge_by, &new, tmp_linpool, 1, &val, 0);
+  /* Actually run the merge rule */
+  enum filter_return fret = f_eval_rte(p->merge_by, &new, tmp_linpool, 1, &val, 0, NULL);
 
   /* Src must be stored now, rte_update2() may return new */
   struct rte_src *new_src = new ? new->src : NULL;
@@ -257,136 +163,6 @@ aggregator_reload_buckets(void *data)
   HASH_WALK_END;
 }
 
-
-/*
- * Evaluate static attribute of @rt1 according to @sa
- * and store result in @pos.
- */
-static void
-eval_static_attr(const struct rte *rt1, struct f_static_attr sa, struct f_val *pos)
-{
-  const struct rta *rta = rt1->attrs;
-
-#define RESULT(_type, value, result)    \
-  do {                                  \
-    pos->type = _type;                  \
-    pos->val.value = result;            \
-  } while (0)
-
-  switch (sa.sa_code)
-  {
-    case SA_NET:       RESULT(sa.f_type, net, rt1->net->n.addr); break;
-    case SA_FROM:       RESULT(sa.f_type, ip, rta->from); break;
-    case SA_GW:                RESULT(sa.f_type, ip, rta->nh.gw); break;
-    case SA_PROTO:         RESULT(sa.f_type, s, rt1->src->proto->name); break;
-    case SA_SOURCE:        RESULT(sa.f_type, i, rta->source); break;
-    case SA_SCOPE:         RESULT(sa.f_type, i, rta->scope); break;
-    case SA_DEST:          RESULT(sa.f_type, i, rta->dest); break;
-    case SA_IFNAME:        RESULT(sa.f_type, s, rta->nh.iface ? rta->nh.iface->name : ""); break;
-    case SA_IFINDEX:   RESULT(sa.f_type, i, rta->nh.iface ? rta->nh.iface->index : 0); break;
-    case SA_WEIGHT:        RESULT(sa.f_type, i, rta->nh.weight + 1); break;
-    case SA_PREF:          RESULT(sa.f_type, i, rta->pref); break;
-    case SA_GW_MPLS:    RESULT(sa.f_type, i, rta->nh.labels ? rta->nh.label[0] : MPLS_NULL); break;
-    default:
-      bug("Invalid static attribute access (%u/%u)", sa.f_type, sa.sa_code);
-  }
-
-#undef RESULT
-}
-
-/*
- * Evaluate dynamic attribute of @rt1 according to @da
- * and store result in @pos.
- */
-static void
-eval_dynamic_attr(const struct rte *rt1, struct f_dynamic_attr da, struct f_val *pos)
-{
-  const struct rta *rta = rt1->attrs;
-  const struct eattr *e = ea_find(rta->eattrs, da.ea_code);
-
-#define RESULT(_type, value, result)    \
-  do {                                  \
-    pos->type = _type;                  \
-    pos->val.value = result;            \
-  } while (0)
-
-#define RESULT_VOID         \
-  do {                      \
-    pos->type = T_VOID;     \
-  } while (0)
-
-  if (!e)
-  {
-    /* A special case: undefined as_path looks like empty as_path */
-    if (da.type == EAF_TYPE_AS_PATH)
-    {
-      RESULT(T_PATH, ad, &null_adata);
-      return;
-    }
-
-    /* The same special case for int_set */
-    if (da.type == EAF_TYPE_INT_SET)
-    {
-      RESULT(T_CLIST, ad, &null_adata);
-      return;
-    }
-
-    /* The same special case for ec_set */
-    if (da.type == EAF_TYPE_EC_SET)
-    {
-      RESULT(T_ECLIST, ad, &null_adata);
-      return;
-    }
-
-    /* The same special case for lc_set */
-    if (da.type == EAF_TYPE_LC_SET)
-    {
-      RESULT(T_LCLIST, ad, &null_adata);
-      return;
-    }
-
-    /* Undefined value */
-    RESULT_VOID;
-    return;
-  }
-
-  switch (e->type & EAF_TYPE_MASK)
-  {
-    case EAF_TYPE_INT:
-      RESULT(da.f_type, i, e->u.data);
-      break;
-    case EAF_TYPE_ROUTER_ID:
-      RESULT(T_QUAD, i, e->u.data);
-      break;
-    case EAF_TYPE_OPAQUE:
-      RESULT(T_ENUM_EMPTY, i, 0);
-      break;
-    case EAF_TYPE_IP_ADDRESS:
-      RESULT(T_IP, ip, *((ip_addr *) e->u.ptr->data));
-      break;
-    case EAF_TYPE_AS_PATH:
-      RESULT(T_PATH, ad, e->u.ptr);
-      break;
-    case EAF_TYPE_BITFIELD:
-      RESULT(T_BOOL, i, !!(e->u.data & (1u << da.bit)));
-      break;
-    case EAF_TYPE_INT_SET:
-      RESULT(T_CLIST, ad, e->u.ptr);
-      break;
-    case EAF_TYPE_EC_SET:
-      RESULT(T_ECLIST, ad, e->u.ptr);
-      break;
-    case EAF_TYPE_LC_SET:
-      RESULT(T_LCLIST, ad, e->u.ptr);
-      break;
-    default:
-      bug("Unknown dynamic attribute type");
-  }
-
-#undef RESULT
-#undef RESULT_VOID
-}
-
 static inline u32 aggr_route_hash(const rte *e)
 {
   struct {
@@ -453,125 +229,29 @@ aggregator_rt_notify(struct proto *P, struct channel *src_ch, net *net, rte *new
     struct lp_state tmp_state;
     lp_save(tmp_linpool, &tmp_state);
 
-    for (uint val_idx = 0; val_idx < p->aggr_on_count; val_idx++)
+    struct rte *rt1 = new;
+    enum filter_return fret = f_eval_rte(p->aggr_on, &new, tmp_linpool, 0, NULL, p->aggr_on_count, tmp_bucket->aggr_data);
+
+    if (rt1 != new)
     {
-      int type = p->aggr_on[val_idx].type;
-      struct f_val *pos = &tmp_bucket->aggr_data[val_idx];
+      rte_free(rt1);
+      log(L_WARN "Aggregator rule modifies the route, reverting");
+    }
 
-      switch (type)
-      {
-        case AGGR_ITEM_TERM: {
-          const struct f_line *line = p->aggr_on[val_idx].line;
-          struct rte *rt1 = new;
-          enum filter_return fret = f_eval_rte(line, &new, tmp_linpool, 0, NULL, pos);
-
-          if (rt1 != new)
-          {
-            rte_free(rt1);
-            log(L_WARN "Aggregator rule modifies the route, reverting");
-          }
-
-          if (fret > F_RETURN)
-            log(L_WARN "%s.%s: Wrong number of items left on stack after evaluation of aggregation list", rt1->src->proto->name, rt1->sender->name);
-
-         switch (pos->type) {
-           case T_VOID:
-           case T_INT:
-           case T_BOOL:
-           case T_PAIR:
-           case T_QUAD:
-           case T_ENUM:
-           case T_IP:
-           case T_EC:
-           case T_LC:
-           case T_RD:
-             /* Fits, OK */
-             break;
-
-           default:
-             log(L_WARN "%s.%s: Expression evaluated to type %s unsupported by aggregator. Store this value as a custom attribute instead", new->src->proto->name, new->sender->name, f_type_name(pos->type));
-             *pos = (struct f_val) { .type = T_INT, .val.i = 0 };
-         }
-
-          break;
-        }
-
-        case AGGR_ITEM_STATIC_ATTR: {
-          eval_static_attr(new, p->aggr_on[val_idx].sa, pos);
-          break;
-        }
-
-        case AGGR_ITEM_DYNAMIC_ATTR: {
-          eval_dynamic_attr(new, p->aggr_on[val_idx].da, pos);
-          break;
-        }
-
-        default:
-          break;
-      }
+    /* Check filter return value */
+    if (fret > F_RETURN)
+    {
+      sl_free(tmp_bucket);
+      lp_restore(tmp_linpool, &tmp_state);
+
+      return;
     }
 
     /* Compute the hash */
     u64 haux;
     mem_hash_init(&haux);
     for (uint i = 0; i < p->aggr_on_count; i++)
-    {
-      mem_hash_mix_num(&haux, tmp_bucket->aggr_data[i].type);
-
-#define MX(k) mem_hash_mix(&haux, &IT(k), sizeof IT(k));
-#define IT(k) tmp_bucket->aggr_data[i].val.k
-
-      switch (tmp_bucket->aggr_data[i].type)
-      {
-       case T_VOID:
-         break;
-       case T_INT:
-       case T_BOOL:
-       case T_PAIR:
-       case T_QUAD:
-       case T_ENUM:
-         MX(i);
-         break;
-       case T_EC:
-       case T_RD:
-         MX(ec);
-         break;
-       case T_LC:
-         MX(lc);
-         break;
-       case T_IP:
-         MX(ip);
-         break;
-       case T_NET:
-         mem_hash_mix_num(&haux, net_hash(IT(net)));
-         break;
-       case T_STRING:
-         mem_hash_mix_str(&haux, IT(s));
-         break;
-       case T_PATH_MASK:
-         mem_hash_mix(&haux, IT(path_mask), sizeof(*IT(path_mask)) + IT(path_mask)->len * sizeof (IT(path_mask)->item));
-         break;
-       case T_PATH:
-       case T_CLIST:
-       case T_ECLIST:
-       case T_LCLIST:
-       case T_BYTESTRING:
-         mem_hash_mix(&haux, IT(ad)->data, IT(ad)->length);
-         break;
-       case T_NONE:
-       case T_PATH_MASK_ITEM:
-       case T_ROUTE:
-       case T_ROUTES_BLOCK:
-         bug("Invalid type %s in hashing", f_type_name(tmp_bucket->aggr_data[i].type));
-       case T_SET:
-         MX(t);
-         break;
-       case T_PREFIX_SET:
-         MX(ti);
-         break;
-      }
-    }
-
+      mem_hash_mix_f_val(&haux, &tmp_bucket->aggr_data[i]);
     tmp_bucket->hash = mem_hash_value(&haux);
 
     /* Find the existing bucket */
@@ -686,8 +366,8 @@ aggregator_init(struct proto_config *CF)
   proto_configure_channel(P, &p->dst, cf->dst);
 
   p->aggr_on_count = cf->aggr_on_count;
-  p->aggr_on_da_count = cf->aggr_on_da_count;
   p->aggr_on = cf->aggr_on;
+  p->premerge = cf->premerge;
   p->merge_by = cf->merge_by;
 
   P->rt_notify = aggregator_rt_notify;
@@ -753,34 +433,16 @@ aggregator_reconfigure(struct proto *P, struct proto_config *CF)
   if (cf->aggr_on_count != p->aggr_on_count)
     return 0;
 
-  if (cf->aggr_on_da_count != p->aggr_on_da_count)
-    return 0;
-
   /* Compare aggregator rule */
-  for (uint i = 0; i < p->aggr_on_count; i++)
-    switch (cf->aggr_on[i].type)
-    {
-      case AGGR_ITEM_TERM:
-       if (!f_same(cf->aggr_on[i].line, p->aggr_on[i].line))
-         return 0;
-       break;
-      case AGGR_ITEM_STATIC_ATTR:
-       if (memcmp(&cf->aggr_on[i].sa, &p->aggr_on[i].sa, sizeof(struct f_static_attr)) != 0)
-         return 0;
-       break;
-      case AGGR_ITEM_DYNAMIC_ATTR:
-       if (memcmp(&cf->aggr_on[i].da, &p->aggr_on[i].da, sizeof(struct f_dynamic_attr)) != 0)
-         return 0;
-       break;
-      default:
-       bug("Broken aggregator rule");
-    }
+  if (!f_same(cf->aggr_on, p->aggr_on) || !f_same(cf->premerge, p->premerge))
+    return 0;
 
   /* Compare merge filter */
   if (!f_same(cf->merge_by, p->merge_by))
     ev_schedule(&p->reload_buckets);
 
   p->aggr_on = cf->aggr_on;
+  p->premerge = cf->premerge;
   p->merge_by = cf->merge_by;
 
   return 1;
index 19459b1d0208cd4575286e1aaae5a5ff77931cbd..7155b19b695dd7c6d37995f77567d4a3fa8bf281 100644 (file)
 struct aggregator_config {
   struct proto_config c;
   struct channel_config *src, *dst;
-  uint aggr_on_count;
-  uint aggr_on_da_count;
-  struct aggr_item *aggr_on;
+  const struct f_line *aggr_on;
+  const struct f_line *premerge;
   const struct f_line *merge_by;
+  uint aggr_on_count;
+  u8 aggr_on_net;
 };
 
 struct aggregator_route {
@@ -54,33 +55,14 @@ struct aggregator_proto {
   slab *route_slab;
 
   /* Aggregator rule */
+  const struct f_line *aggr_on;
   uint aggr_on_count;
-  uint aggr_on_da_count;
-  struct aggr_item *aggr_on;
+  u8 aggr_on_net;
 
   /* Merge filter */
+  const struct f_line *premerge;
   const struct f_line *merge_by;
   event reload_buckets;
 };
 
-enum aggr_item_type {
-  AGGR_ITEM_TERM,
-  AGGR_ITEM_STATIC_ATTR,
-  AGGR_ITEM_DYNAMIC_ATTR,
-};
-
-struct aggr_item {
-  enum aggr_item_type type;
-  union {
-    struct f_static_attr sa;
-    struct f_dynamic_attr da;
-    const struct f_line *line;
-  };
-};
-
-struct aggr_item_node {
-  const struct aggr_item_node *next;
-  struct aggr_item i;
-};
-
 #endif
index d7d9ae53eb08dadc3cb40225c608145941e26173..ba4eb9c456b9119f6a4748c0f638b4dbed01362b 100644 (file)
@@ -17,12 +17,11 @@ CF_DEFINES
 #define AGGREGATOR_CFG ((struct aggregator_config *) this_proto)
 #define AGGR_ITEM_ALLOC ((struct aggr_item_node *) cfg_allocz(sizeof(struct aggr_item_node)))
 
-
 CF_DECLS
 
 CF_KEYWORDS(AGGREGATOR, AGGREGATE, ON, MERGE, BY)
 
-%type <ai> aggr_item aggr_list
+%type <xp> aggr_item aggr_list
 
 CF_GRAMMAR
 
@@ -41,42 +40,31 @@ aggregator_proto_item:
    proto_item
  | channel_item_
  | PEER TABLE rtable { AGGREGATOR_CFG->dst->table = $3; }
- | AGGREGATE ON aggr_list {
+ | AGGREGATE ON {
     if (AGGREGATOR_CFG->aggr_on)
       cf_error("Only one aggregate on clause allowed");
 
-    _Bool net_present = 0;
-    int count = 0;
-
-    for (const struct aggr_item_node *item = $3; item; item = item->next) {
-//      log(L_WARN "type %d sacode %d", item->i.type, item->i.sa.sa_code);
-      if (item->i.type == AGGR_ITEM_STATIC_ATTR && item->i.sa.sa_code == SA_NET)
-       net_present = 1;
-
-      count++;
-    }
+    cf_push_block_scope(new_config);
+ } aggr_list {
+    int count = new_config->current_scope->slots;
+    cf_pop_block_scope(new_config);
 
-   if (!net_present)
-     cf_error("'NET' must be present");
+    if (!AGGREGATOR_CFG->aggr_on_net)
+      cf_error("aggregate on must be always include 'net'.");
 
-   AGGREGATOR_CFG->aggr_on = cfg_alloc(sizeof(struct aggr_item) * count);
-
-   int pos = 0;
-   for (const struct aggr_item_node *item = $3; item; item = item->next) {
-     if (item->i.type == AGGR_ITEM_DYNAMIC_ATTR)
-       AGGREGATOR_CFG->aggr_on_da_count++;
-
-     AGGREGATOR_CFG->aggr_on[pos++] = item->i;
-   }
+   AGGREGATOR_CFG->aggr_on_count = count;
+   AGGREGATOR_CFG->aggr_on = f_linearize($4.begin, count);
 
-   AGGREGATOR_CFG->aggr_on_count = pos;
+   struct f_line *premerge = f_linearize($4.end, 0);
+   premerge->args = count;
+   AGGREGATOR_CFG->premerge = premerge;
  }
  | MERGE BY {
    cf_push_block_scope(new_config);
    f_predefined_variable(new_config, "routes", T_ROUTES_BLOCK);
  } function_body {
    cf_pop_block_scope(new_config);
-   $4->args++; /* The predefined "routes" variable */
+   $4->args++;
    AGGREGATOR_CFG->merge_by = $4;
  }
 ;
@@ -88,45 +76,61 @@ aggregator_proto: aggregator_proto_start proto_name '{' aggregator_proto_opts '}
 aggr_list:
    aggr_item
  | aggr_list ',' aggr_item {
-       if ($3 == NULL) {
-         $$ = $1;
-       } else {
-         $$ = $3;
-         $$->next = $1;
-       }
-   }
+    if ($$.begin = $3.begin)
+      $$.begin->next = $1.begin;
+    else
+      $$.begin = $1.begin;
+
+    if ($$.end = $3.end)
+      $$.end->next = $1.end;
+    else
+      $$.end = $1.end;
+ }
  ;
 
 aggr_item:
    '(' term ')' {
-       $$ = AGGR_ITEM_ALLOC;
-       $$->i.type = AGGR_ITEM_TERM;
-       $$->i.line = f_linearize($2, 1);
-    }
-  | CF_SYM_KNOWN {
-      switch ($1->class) {
-        case SYM_ATTRIBUTE:
-          $$ = AGGR_ITEM_ALLOC;
-          $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
-          $$->i.da = *$1->attribute;
-          break;
-        case SYM_CONSTANT_RANGE:
-          $$ = NULL;
-          break;
-        default:
-          cf_error("Can't aggregate on symbol type %s.", cf_symbol_class_name($1));
-      }
-    }
-  | dynamic_attr {
-      $$ = AGGR_ITEM_ALLOC;
-      $$->i.type = AGGR_ITEM_DYNAMIC_ATTR;
-      $$->i.da = $1;
-    }
-  | static_attr {
-      $$ = AGGR_ITEM_ALLOC;
-      $$->i.type = AGGR_ITEM_STATIC_ATTR;
-      $$->i.sa = $1;
+     switch ($2->type) {
+       case T_INT:
+       case T_BOOL:
+       case T_PAIR:
+       case T_QUAD:
+       case T_ENUM:
+       case T_IP:
+       case T_EC:
+       case T_LC:
+       case T_RD:
+        /* Fits, OK */
+        break;
+
+       default:
+        cf_error("Expression evaluated to type %s unsupported by aggregator. Store this value as a custom attribute instead", f_type_name($2->type));
+     }
+
+     $$.begin = $2;
+     $$.end = NULL;
+     f_new_var(new_config->current_scope);
+   }
+  | lvalue {
+    $$.begin = f_lval_getter(&$1);
+    int vari = f_new_var(new_config->current_scope);
+
+    if ($1.type == F_LVAL_SA && $1.sa.sa_code == SA_NET)
+      AGGREGATOR_CFG->aggr_on_net = 1;
+    if (($1.type == F_LVAL_CONSTANT) || 
+       ($1.type == F_LVAL_SA && $1.sa.readonly))
+      $$.end = NULL;
+    else
+    {
+      char varname[12];
+      bsnprintf(varname, 12, "!aggr%d", vari);
+      $$.end = f_lval_setter(&$1,
+         f_new_inst(FI_VAR_GET, cf_define_symbol(
+             new_config, cf_get_symbol(new_config, varname),
+             SYM_VARIABLE | $$.begin->type, offset, vari
+             )));
     }
+  }
   ;
 
 CF_CODE
index e5e1e267230df73a95b04fc8f93357036a2ee5fd..ec7afc4fea4b3a0c5c0e0f189a2c5faa2387ec9a 100644 (file)
@@ -100,7 +100,7 @@ protocol aggregator {
   table master6;
   peer table agr_result;
   export all;
-  aggregate on net,(defined(bgp_med));
+  aggregate on net,(defined(bgp_med)), (1 + 3 + 5 + 7), preference, dest;
   merge by {
     print "Merging all these: ", routes;
     bgp_med = 0;