--- filter/data.h
+++ filter/data.h
@@ -28,6 +28,7 @@
   T_BOOL = 0x11,
   T_PAIR = 0x12,  /*	Notice that pair is stored as integer: first << 16 | second */
   T_QUAD = 0x13,
+  T_AGGREGATOR = 0x14,
 
 /* Put enumerational types in 0x30..0x3f range */
   T_ENUM_LO = 0x30,
@@ -83,6 +84,10 @@
   enum f_type type;	/* T_*  */
   union {
     uint i;
+    struct {
+      u32 asn;
+      u32 rid;
+    } aggregator;
     u64 ec;
     lcomm lc;
     vpn_rd rd;
--- filter/data.c
+++ filter/data.c
@@ -32,6 +32,7 @@
   [T_INT]	= "int",
   [T_BOOL]	= "bool",
   [T_PAIR]	= "pair",
+  [T_AGGREGATOR] = "aggregator",
   [T_QUAD]	= "quad",
 
   [T_ENUM_RTS]	= "enum rts",
@@ -163,6 +164,16 @@
   return 0;
 }
 
+static inline int
+aggregator_cmp(const struct f_val *v1, const struct f_val *v2)
+{
+  if (v1->val.aggregator.asn != v2->val.aggregator.asn)
+    return (v1->val.aggregator.asn > v2->val.aggregator.asn) ? 1 : -1;
+  if (v1->val.aggregator.rid != v2->val.aggregator.rid)
+    return (v1->val.aggregator.rid > v2->val.aggregator.rid) ? 1 : -1;
+  return 0;
+}
+
 /**
  * val_compare - compare two values
  * @v1: first value
@@ -205,6 +216,8 @@
     return u64_cmp(v1->val.ec, v2->val.ec);
   case T_LC:
     return lcomm_cmp(v1->val.lc, v2->val.lc);
+  case T_AGGREGATOR:
+    return aggregator_cmp(v1, v2);
   case T_IP:
     return ipa_compare(v1->val.ip, v2->val.ip);
   case T_NET:
@@ -631,6 +644,7 @@
   case T_IP:	buffer_print(buf, "%I", v->val.ip); return;
   case T_NET:   buffer_print(buf, "%N", v->val.net); return;
   case T_PAIR:	buffer_print(buf, "(%u,%u)", v->val.i >> 16, v->val.i & 0xffff); return;
+  case T_AGGREGATOR: buffer_print(buf, "aggregator(%u, %R)", v->val.aggregator.asn, v->val.aggregator.rid); return;
   case T_QUAD:	buffer_print(buf, "%R", v->val.i); return;
   case T_EC:	ec_format(buf2, v->val.ec); buffer_print(buf, "%s", buf2); return;
   case T_LC:	lc_format(buf2, v->val.lc); buffer_print(buf, "%s", buf2); return;
--- filter/config.Y
+++ filter/config.Y
@@ -458,6 +458,7 @@
  | RD { $$ = T_RD; }
  | PREFIX { $$ = T_NET; }
  | PAIR { $$ = T_PAIR; }
+ | AGGREGATOR { $$ = T_AGGREGATOR; }
  | QUAD { $$ = T_QUAD; }
  | EC { $$ = T_EC; }
  | LC { $$ = T_LC; }
@@ -821,6 +822,7 @@
 
 constructor:
    '(' term ',' term ')' { $$ = f_new_inst(FI_PAIR_CONSTRUCT, $2, $4); }
+ | AGGREGATOR '(' term ',' term ')' { $$ = f_new_inst(FI_AGGREGATOR_CONSTRUCT, $3, $5); }
  | '(' ec_kind ',' term ',' term ')' { $$ = f_new_inst(FI_EC_CONSTRUCT, $4, $6, $2); }
  | '(' term ',' term ',' term ')' { $$ = f_new_inst(FI_LC_CONSTRUCT, $2, $4, $6); }
  | bgp_path { $$ = f_new_inst(FI_PATHMASK_CONSTRUCT, $1); }
--- filter/f-inst.c
+++ filter/f-inst.c
@@ -335,6 +335,22 @@
     RESULT(T_PAIR, i, (u1 << 16) | u2);
   }
 
+  INST(FI_AGGREGATOR_CONSTRUCT, 2, 1) {
+    ARG(1, T_INT);
+    ARG_ANY(2);
+
+    u32 rid;
+
+    if (v2.type == T_QUAD)
+      rid = v2.val.i;
+    else if (val_is_ip4(&v2))
+      rid = ipa_to_u32(v2.val.ip);
+    else
+      runtime("Argument 2 of aggregator constructor must be router ID or IPv4 address, got 0x%02x", v2.type);
+
+    RESULT(T_AGGREGATOR, aggregator, ((typeof(v1.val.aggregator)) { .asn = v1.val.i, .rid = rid }));
+  }
+
   INST(FI_EC_CONSTRUCT, 2, 1) {
     ARG_ANY(1);
     ARG(2, T_INT);
@@ -880,6 +896,12 @@
       case EAF_TYPE_OPAQUE:
 	if (da.f_type == T_ENUM_EMPTY)
 	  RESULT_(T_ENUM_EMPTY, i, 0);
+	else if (da.f_type == T_AGGREGATOR)
+	  RESULT_(T_AGGREGATOR, aggregator,
+	    ((typeof(v1.val.aggregator)) {
+	      .asn = get_u32(e->u.ptr->data + 0),
+	      .rid = get_u32(e->u.ptr->data + 4),
+	    }));
 	else
 	  RESULT_(T_BYTESTRING, ad, e->u.ptr);
 	break;
@@ -950,6 +972,15 @@
 	break;
 
       case EAF_TYPE_OPAQUE:
+	if (da.f_type == T_AGGREGATOR)
+	{
+	  struct adata *ad = lp_alloc_adata(fs->pool, 8);
+	  put_u32(ad->data + 0, v1.val.aggregator.asn);
+	  put_u32(ad->data + 4, v1.val.aggregator.rid);
+	  l->attrs[0].u.ptr = ad;
+	  break;
+	}
+	/* Fall through */
       case EAF_TYPE_AS_PATH:
       case EAF_TYPE_INT_SET:
       case EAF_TYPE_EC_SET:
@@ -1120,6 +1151,12 @@
   /* Get data part from the standard community */
   METHOD_R(T_PAIR, data, T_INT, i, v1.val.i & 0xFFFF);
 
+  /* Get ASN part from aggregator */
+  METHOD_R(T_AGGREGATOR, asn, T_INT, i, v1.val.aggregator.asn);
+
+  /* Get router ID part from aggregator */
+  METHOD_R(T_AGGREGATOR, router_id, T_QUAD, i, v1.val.aggregator.rid);
+
   /* Get ASN part from the large community */
   METHOD_R(T_LC, asn, T_INT, i, v1.val.lc.asn);
 
--- proto/bgp/config.Y
+++ proto/bgp/config.Y
@@ -511,7 +511,7 @@
 dynamic_attr: BGP_ATOMIC_AGGR
 	{ $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_ATOMIC_AGGR)); $$.flags = BAF_TRANSITIVE; } ;
 dynamic_attr: BGP_AGGREGATOR
-	{ $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_ENUM_EMPTY, EA_CODE(PROTOCOL_BGP, BA_AGGREGATOR));	$$.flags = BAF_OPTIONAL | BAF_TRANSITIVE; } ;
+	{ $$ = f_new_dynamic_attr(EAF_TYPE_OPAQUE, T_AGGREGATOR, EA_CODE(PROTOCOL_BGP, BA_AGGREGATOR));	$$.flags = BAF_OPTIONAL | BAF_TRANSITIVE; } ;
 dynamic_attr: BGP_COMMUNITY
 	{ $$ = f_new_dynamic_attr(EAF_TYPE_INT_SET, T_CLIST, EA_CODE(PROTOCOL_BGP, BA_COMMUNITY));	$$.flags = BAF_OPTIONAL | BAF_TRANSITIVE; } ;
 dynamic_attr: BGP_ORIGINATOR_ID
--- filter/test.conf
+++ filter/test.conf
@@ -1056,6 +1056,29 @@
 
 
 /*
+ *	Testing Aggregator
+ *	------------------
+ */
+
+function t_aggregator()
+aggregator a;
+{
+	a = aggregator(64512, 192.0.2.1);
+	bt_assert(format(a) = "aggregator(64512, 192.0.2.1)");
+	bt_assert(a.asn = 64512);
+	bt_assert(a.router_id = 192.0.2.1);
+
+	a = aggregator(64513, 10.20.30.40);
+	bt_assert(a.asn = 64513);
+	bt_assert(a.router_id = 10.20.30.40);
+}
+
+bt_test_suite(t_aggregator, "Testing aggregator");
+
+
+
+
+/*
  * 	Testing Community List
  *	----------------------
  */
@@ -2633,6 +2656,7 @@
 
 	bt_check_assign(from, 10.20.30.40);
 	# bt_check_assign(gw, 55.55.55.44);
+	bt_check_assign(bgp_aggregator, aggregator(64512, 10.20.30.40));
 
 	bgp_community.add((3,5));
 	bgp_ext_community.add((ro, 135, 999));
--- doc/bird.sgml
+++ doc/bird.sgml
@@ -4102,10 +4102,11 @@
 	presence of which indicates that the route has been aggregated from
 	multiple routes by some router on the path from the originator.
 
-	<tag><label id="rta-bgp-aggregator">void bgp_aggregator [O]</tag>
+	<tag><label id="rta-bgp-aggregator">aggregator bgp_aggregator [O]</tag>
 	This is an optional attribute specifying AS number and IP address of the
 	BGP router that created the route by aggregating multiple BGP routes.
-	Currently, the attribute is not accessible from filters.
+	It is accessible from filters and can be set using
+	<cf/aggregator(asn, router_id)/>.
 
 	<tag><label id="rta-bgp-community">clist bgp_community [O]</tag>
 	List of community values associated with the route. Each such value is a
