From c407778be84b67fc1fee8589a99dced2af7c2ec5 Mon Sep 17 00:00:00 2001
From: "Alexander V. Chernikov" <melifaro@ipfw.ru>
Date: Thu, 12 Feb 2015 03:19:34 +0300
Subject: [PATCH 6/6] Implement dynamic BGP templates permitting to specify IP
 ranges inside templates instead of per-neighbor record.

---
 conf/cf-lex.l      |   2 +
 conf/conf.h        |   4 +
 nest/config.Y      |   4 +-
 nest/proto.c       | 141 ++++++++++++++++++++++++-
 nest/protocol.h    |   7 +-
 proto/bgp/bgp.c    | 297 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 proto/bgp/bgp.h    |   9 ++
 proto/bgp/config.Y |  15 ++-
 8 files changed, 475 insertions(+), 4 deletions(-)

diff --git a/conf/cf-lex.l b/conf/cf-lex.l
index b995e10..dfcee5f 100644
--- a/conf/cf-lex.l
+++ b/conf/cf-lex.l
@@ -657,6 +657,8 @@ cf_symbol_class_name(struct symbol *sym)
       return "protocol";
     case SYM_TEMPLATE:
       return "protocol template";
+    case SYM_DYNTEMPLATE:
+      return "dynamic template";
     case SYM_FUNCTION:
       return "function";
     case SYM_FILTER:
diff --git a/conf/conf.h b/conf/conf.h
index eb8acb5..7827280 100644
--- a/conf/conf.h
+++ b/conf/conf.h
@@ -14,6 +14,7 @@
 
 
 /* Configuration structure */
+struct bgp_protocol_cfg;
 
 struct config {
   pool *pool;				/* Pool the configuration is stored in */
@@ -51,6 +52,8 @@ struct config {
   int obstacle_count;			/* Number of items blocking freeing of this config */
   int shutdown;				/* This is a pseudo-config for daemon shutdown */
   bird_clock_t load_time;		/* When we've got this configuration */
+
+  struct bgp_protocol_cfg *bgp_cfg;	/* generic storage for protocol specific data */
 };
 
 /* Please don't use these variables in protocols. Use proto_config->global instead. */
@@ -124,6 +127,7 @@ struct symbol {
 #define SYM_FILTER 4
 #define SYM_TABLE 5
 #define SYM_ROA 6
+#define SYM_DYNTEMPLATE 7
 
 #define SYM_VARIABLE 0x100	/* 0x100-0x1ff are variable types */
 #define SYM_CONSTANT 0x200	/* 0x200-0x2ff are variable types */
diff --git a/nest/config.Y b/nest/config.Y
index 481d9f5..095a8fe 100644
--- a/nest/config.Y
+++ b/nest/config.Y
@@ -63,7 +63,7 @@ CF_KEYWORDS(PASSWORD, FROM, PASSIVE, TO, ID, EVENTS, PACKETS, PROTOCOLS, INTERFA
 CF_KEYWORDS(PRIMARY, STATS, COUNT, FOR, COMMANDS, PREEXPORT, NOEXPORT, GENERATE, ROA)
 CF_KEYWORDS(LISTEN, BGP, V6ONLY, DUAL, ADDRESS, PORT, PASSWORDS, DESCRIPTION, SORTED)
 CF_KEYWORDS(RELOAD, IN, OUT, MRTDUMP, MESSAGES, RESTRICT, MEMORY, IGP_METRIC, CLASS, DSCP)
-CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, FLUSH, AS)
+CF_KEYWORDS(GRACEFUL, RESTART, WAIT, MAX, FLUSH, AS, DYNAMIC)
 
 CF_ENUM(T_ENUM_RTS, RTS_, DUMMY, STATIC, INHERIT, DEVICE, STATIC_DEVICE, REDIRECT,
 	RIP, OSPF, OSPF_IA, OSPF_EXT1, OSPF_EXT2, BGP, PIPE)
@@ -171,6 +171,7 @@ CF_ADDTO(conf, proto)
 proto_start:
    PROTOCOL { $$ = SYM_PROTO; }
  | TEMPLATE { $$ = SYM_TEMPLATE; }
+ | DYNAMIC TEMPLATE { $$ = SYM_DYNTEMPLATE; }
  ;
 
 proto_name:
@@ -191,6 +192,7 @@ proto_name:
      this_proto->name = $1->name;
 
      proto_copy_config(this_proto, $3->def);
+     this_proto->class = $1->class;
    }
  ;
 
diff --git a/nest/proto.c b/nest/proto.c
index 846d172..eaeeaed 100644
--- a/nest/proto.c
+++ b/nest/proto.c
@@ -398,6 +398,141 @@ proto_init(struct proto_config *c)
   return q;
 }
 
+/**
+ * proto_create_dynamic - Creates dynamic protocol instance
+ * @tc = config template to use
+ * @pname - instance name
+ * @pi - pointer to protocol-specific data
+ *
+ * Function creates all atrributes for protocol - symbol of
+ * appropriate type, protocol config and protocol itself.
+ * Protocol is started after this.
+ *
+ * Returns pointer to protocol instance or NULL.
+ */
+struct proto *
+proto_create_dynamic(struct proto_config *tc, char *pname, void *pi)
+{
+  struct proto_config *c;
+  struct proto *p = NULL;
+  struct protocol *q;
+  struct symbol *sym;
+
+  q = tc->protocol;
+
+  if (!q->copy_dynconfig)
+    return NULL;
+
+  config_start_edit(config);
+
+  sym = cf_find_symbol(config, pname);
+  if (!sym || sym->class != SYM_VOID)
+    {
+      config_stop_edit();
+      log(L_ERR "Unable to create symbol %s for dynamic %s protocol", pname, q->name);
+      return NULL;
+    }
+  sym->class = SYM_PROTO;
+
+  c = proto_create_config(config, q, SYM_PROTO);
+  proto_copy_config(c, tc);
+  c->name = sym->name;
+  c->dyn_parent = tc;
+  q->copy_dynconfig(c, NULL, pi);
+
+  p = proto_init(c);
+  config_stop_edit();
+
+  if (!c)
+    {
+      log(L_ERR "Creating dynamic %s config for %s failed", q->name, pname);
+      return NULL;
+    }
+
+  if (!p)
+    {
+      log(L_ERR "Creating dynamic %s protocol instance %s failed", q->name, pname);
+      return NULL;
+    }
+
+  /* Start protocol */
+  proto_rethink_goal(p);
+
+  return p;
+}
+
+/**
+ * proto_reconfigure_dynamic - creates dynamic protocol configuration on request
+ * @p - protocol instance
+ *
+ * Function checks is the protocol is really dynamic and it dynamic
+ * template still exists in new config. If true, it checks if the original
+ * creation conditions still holds by running @retest_dynconfig callback.
+ * At the end, new configuration is created based on new template and running
+ * protocol data.
+ *
+ * Returns pointer to proto_config or NULL,
+ */
+struct proto_config *
+proto_reconfigure_dynamic(struct config *new, struct proto *p)
+{
+  struct proto_config *oc, *nc, *c;
+  struct symbol *sym;
+
+  DBG("Dynamic protocol %s reconfiguration check\n", p->name);
+  /* Try to find parent dynamic template in new config */
+  oc = p->cf->dyn_parent;
+  sym = cf_find_symbol(new, oc->name);
+  if (!sym || sym->class != SYM_DYNTEMPLATE)
+    {
+      DBG("Wrong dyntemplate symbol %s class: %d(%p)\n", oc->name, sym ? sym->class : 0, sym);
+      return NULL;
+    }
+  /* Found parent configuration */
+  nc = sym->def;
+
+  /* Check if dynamic template protocol is the same */
+  if (oc->protocol != nc->protocol)
+    {
+      DBG("Wrong dyntemplate protocol: %s vs %s\n", oc->protocol->name, nc->protocol->name);
+      return NULL;
+    }
+
+  /* Check if condition still holds */
+  if (!nc->protocol->retest_dynconfig(p, nc))
+    {
+      DBG("retest dynconfig failed\n");
+      return NULL;
+    }
+
+  DBG("Start of dynamic protocol %s reconfiguration\n", p->name);
+  config_start_edit(new);
+  /*
+   * Check if address condition still holds
+   * and create config if true
+   */
+  sym = cf_find_symbol(new, p->name);
+  if (!sym || sym->class != SYM_VOID)
+    {
+      config_stop_edit();
+      log(L_ERR "Unable to create symbol %s for dynamic %s protocol",
+       p->name, nc->protocol->name);
+      return NULL;
+    }
+  sym->class = SYM_PROTO;
+
+  /* Create new config based on template */
+  c = proto_create_config(new, nc->protocol, SYM_PROTO);
+  proto_copy_config(c, nc);
+  c->name = sym->name;
+  c->dyn_parent = nc;
+  /* Do necessary customizations */
+  nc->protocol->copy_dynconfig(c, p, NULL);
+
+  config_stop_edit();
+  return c;
+}
+
 int proto_reconfig_type;  /* Hack to propagate type info to pipe reconfigure hook */
 
 static int
@@ -521,13 +656,17 @@ protos_commit(struct config *new, struct config *old, int force_reconfig, int ty
     {
       WALK_LIST(oc, old->protos)
 	{
+	  nc = NULL;
 	  p = oc->proto;
 	  sym = cf_find_symbol(new, oc->name);
 	  if (sym && sym->class == SYM_PROTO && !new->shutdown)
+	    nc = sym->def;
+	  if (!nc && oc->dyn_parent)
+	    nc = proto_reconfigure_dynamic(new, p);
+	  if (nc)
 	    {
 	      /* Found match, let's check if we can smoothly switch to new configuration */
 	      /* No need to check description */
-	      nc = sym->def;
 	      nc->proto = p;
 
 	      /* We will try to reconfigure protocol p */
diff --git a/nest/protocol.h b/nest/protocol.h
index a20b053..b2c88e5 100644
--- a/nest/protocol.h
+++ b/nest/protocol.h
@@ -57,6 +57,8 @@ struct protocol {
   int (*get_attr)(struct eattr *, byte *buf, int buflen);	/* ASCIIfy dynamic attribute (returns GA_*) */
   void (*show_proto_info)(struct proto *);	/* Show protocol info (for `show protocols all' command) */
   void (*copy_config)(struct proto_config *, struct proto_config *);	/* Copy config from given protocol instance */
+  void (*copy_dynconfig)(struct proto_config *, struct proto *, void *);    /* Clone config from given dynamic template */
+  int (*retest_dynconfig)(struct proto *, struct proto_config *);
 };
 
 void protos_build(void);
@@ -65,6 +67,8 @@ void protos_preconfig(struct config *);
 void protos_postconfig(struct config *);
 void protos_commit(struct config *new, struct config *old, int force_restart, int type);
 void protos_dump_all(void);
+struct proto *proto_create_dynamic(struct proto_config *tc, char *pname, void *pi);
+
 
 #define GA_UNKNOWN	0		/* Attribute not recognized */
 #define GA_NAME		1		/* Result = name */
@@ -89,7 +93,7 @@ struct proto_config {
   struct proto *proto;			/* Instance we've created */
   char *name;
   char *dsc;
-  int class;				/* SYM_PROTO or SYM_TEMPLATE */
+  int class;				/* SYM_PROTO or SYM_TEMPLATE or SYM_DYNTEMPLATE */
   u32 debug, mrtdump;			/* Debugging bitfields, both use D_* constants */
   unsigned preference, disabled;	/* Generic parameters */
   int in_keep_filtered;			/* Routes rejected in import filter are kept */
@@ -100,6 +104,7 @@ struct proto_config {
 					   (relevant when in_keep_filtered is active) */
   struct proto_limit *in_limit;		/* Limit for importing routes from protocol */
   struct proto_limit *out_limit;	/* Limit for exporting routes to protocol */
+  struct proto_config *dyn_parent;	/* Parent dynamic template */
 
   /* Check proto_reconfigure() and proto_copy_config() after changing struct proto_config */
 
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 0fd2b68..5ead5ad 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -71,6 +71,7 @@
 #include "nest/route.h"
 #include "nest/cli.h"
 #include "nest/locks.h"
+#include "filter/filter.h"
 #include "conf/conf.h"
 #include "lib/socket.h"
 #include "lib/resource.h"
@@ -89,6 +90,39 @@ static void bgp_active(struct bgp_proto *p);
 static sock *bgp_setup_listen_sk(ip_addr addr, unsigned port, u32 flags);
 static void bgp_update_bfd(struct bgp_proto *p, int use_bfd);
 
+struct bgp_addr_node {
+  struct f_trie_node tn;		/* Information about network */
+  struct bgp_config *c;			/* Pointer to protocol config */
+  node addr_node;			/* Member of global lladdr_list */
+  node allow_node;			/* Member of proto allow_list */
+};
+
+struct bgp_dtemplate_pinfo {
+  ip_addr addr;
+};
+
+/**
+ * bgp_get_protocol_config - sets up BGP-specific parameters storage in config
+ *
+ */
+static struct bgp_protocol_cfg *
+bgp_get_protocol_config(struct config *new)
+{
+  struct bgp_protocol_cfg *bgp_cfg;
+
+  bgp_cfg = (struct bgp_protocol_cfg *)new->bgp_cfg;
+  if (!bgp_cfg)
+    {
+      bgp_cfg = cfl_allocz(new, sizeof(struct bgp_protocol_cfg));
+      bgp_cfg->addr_trie = f_new_trie(new->mem, sizeof(struct bgp_addr_node));
+      init_list(&bgp_cfg->lladdr_list);
+      new->bgp_cfg = bgp_cfg;
+      DBG("Set up new bgp-specific config\n");
+    }
+
+  return bgp_cfg;
+}
+
 
 /**
  * bgp_open - open a BGP instance
@@ -712,6 +746,223 @@ bgp_connect(struct bgp_proto *p)	/* Enter Connect state and start establishing c
   return;
 }
 
+static int
+bgp_print_template(char *buf, int size, const char *fmt, ip_addr addr)
+{
+  int len, off;
+  u32 a;
+  char *str;
+  char ipbuf[STD_ADDRESS_P_LENGTH+1];
+
+  for (str=buf ; *fmt ; ++fmt)
+    {
+      if (*fmt != '%')
+	{
+	  if (!size)
+	    return -1;
+	  *str++ = *fmt;
+	  size--;
+	  continue;
+	}
+      ++fmt;
+      len = 0;
+
+      switch (*fmt)
+        {
+	/* Part of IPv4/IPv6 address */
+	case '1':
+	case '2':
+	case '3':
+	case '4':
+#ifdef IPV6
+	case '5':
+	case '6':
+	case '7':
+	case '8':
+	  off = (*fmt - '1');
+	  a = addr.addr[off / 2];
+	  a = ((off % 2) ? a : (a >> 16)) & 0xffff;
+	  len = bsnprintf(ipbuf, sizeof(ipbuf), "%x", a);
+	  buf += len;
+	  size -= len;
+#else
+	  off = 32 - (*fmt - '0') * 8;
+	  a = ipa_to_u32(addr);
+	  a = (a >> off) & 0xFF;
+	  len = bsnprintf(ipbuf, sizeof(ipbuf), "%u", a);
+	  buf += len;
+	  size -= len;
+#endif
+	  break;
+
+	/* IP address */
+	case 'I':
+	  ip_ntop(addr, ipbuf);
+	  len = strlen(ipbuf);
+	  break;
+	}
+
+      if (len > size)
+	return -1;
+      if (len)
+	{
+	  strcpy(str, ipbuf);
+	  str += len;
+	}
+    }
+
+    if (!size)
+      return -1;
+    *str = '\0';
+    return str-buf;
+}
+
+/**
+ * bgp_create_proto - creates new dynamic instance
+ * @tc - pointer to template config
+ * @sk - socket
+ *
+ * Functions compiles protocol name based ot template and address
+ * and tries to create protocol instance itself.
+ *
+ * Returns pointer to instance or NULL.
+ */
+static struct bgp_proto *
+bgp_create_proto(struct bgp_config *tc, sock *sk)
+{
+  struct bgp_dtemplate_pinfo pi;
+  struct bgp_proto *p;
+  char pname[SYM_MAX_LEN];
+
+  if (bgp_print_template(pname, sizeof(pname), tc->dyn_nameformat, sk->daddr) == -1)
+    {
+      log(L_ERR "Unable to name connection from %I based on template %s", sk->daddr, tc->c.name);
+      return NULL;
+    }
+  log(L_ERR "Creating new BGP protocol %s from template %s and incoming connection %I",
+    pname, tc->c.name, sk->daddr);
+
+  pi.addr = sk->daddr;
+
+  p = (struct bgp_proto *)proto_create_dynamic(&tc->c, pname, &pi);
+  if (!p)
+    return NULL;
+
+  p->start_state = p->cf->capabilities ? BSS_CONNECT : BSS_CONNECT_NOCAP;
+
+  return p;
+}
+
+/**
+ * bgp_add_allow_mask - link new matching CIDR to the dynamic template
+ * @new - global config
+ * @c - bgp config
+ * @addr - prefix address
+ * @plen - mask length
+ *
+ * For addresses with link scope, add record to global linked list.
+ * For global scope, add record to global trie.
+ * In any case, link record to template allow_list.
+ */
+void
+bgp_add_allow_mask(struct config *new, struct bgp_config *c, ip_addr addr, int plen)
+{
+  struct bgp_protocol_cfg *bgp_cfg;
+  struct bgp_addr_node *an;
+
+  bgp_cfg = bgp_get_protocol_config(new);
+
+  /* Handle link-local addresses differently */
+  if (ipa_has_link_scope(addr))
+    {
+      an = (struct bgp_addr_node *)cfl_allocz(new, sizeof(struct bgp_addr_node));
+      an->tn.addr = addr;
+      an->tn.plen = plen;
+      an->c = c;
+      add_tail(&bgp_cfg->lladdr_list, &an->addr_node);
+      add_tail(&c->dyn_allow_list, &an->allow_node);
+      return;
+    }
+
+  an = (struct bgp_addr_node *)trie_add_prefix(bgp_cfg->addr_trie,
+    addr, plen, plen, MAX_PREFIX_LENGTH);
+  if (an->c)
+    cf_error("Prefix %I/%d already exists from %s", addr, plen, an->c->c.name);
+  add_tail(&c->dyn_allow_list, &an->allow_node);
+  an->c = c;
+}
+
+static void
+bgp_copy_dynconfig(struct proto_config *c, struct proto *p, void *data)
+{
+  struct bgp_config *new, *oc;
+  struct bgp_dtemplate_pinfo *pi;
+
+  new = (struct bgp_config *)c;
+
+  /* The only thing we have to copy is source address */
+  if (p)
+    {
+      oc = (struct bgp_config *)p->cf;
+      new->remote_ip = oc->remote_ip;
+    }
+  else
+    {
+      pi = (struct bgp_dtemplate_pinfo *)data;
+      new->remote_ip = pi->addr;
+    }
+}
+
+/**
+ * bgp_retest_dynconfig - checks if dynamic instance is still relevant
+ * @p - protocol instance in question
+ * @c - new template to check with
+ *
+ * Checks if @p remote address still matches prefix lists and
+ * interface in @c. Checks also if protocol was used recently.
+ *
+ * Returns non-zero if condition still holds/
+ */
+static int
+bgp_retest_dynconfig(struct proto *p, struct proto_config *c)
+{
+  struct bgp_config *oc, *nc;
+  struct bgp_addr_node *an;
+  node *n;
+  int found;
+
+  nc = (struct bgp_config *)c;
+  oc = (struct bgp_config *)p->cf;
+
+  if (oc->iface != nc->iface)
+    {
+      log(L_ERR "Interface changed for dynamic proto %s: %s vs %s",
+       p->name, oc->iface ? oc->iface->name : "NULL",
+       nc->iface ? nc->iface->name : "NULL");
+      return 0;
+    }
+
+  /* Check all dynamic prefix masks */
+  found = 0;
+  WALK_LIST(n, nc->dyn_allow_list)
+    {
+      an = SKIP_BACK(struct bgp_addr_node, allow_node, n);
+      if (ipa_in_net(oc->remote_ip, an->tn.addr, an->tn.plen))
+       {
+         found = 1;
+         break;
+       }
+    }
+  if (!found)
+    {
+      log(L_ERR "No more matching masks for dynamic proto %s in template %s",
+       p->name, nc->c.name);
+      return 0;
+    }
+
+  return 1;
+}
+
 /**
  * bgp_find_proto - find existing proto by socket
  * @sk: TCP socket
@@ -721,15 +972,49 @@ static struct bgp_proto *
 bgp_find_proto(sock *sk)
 {
   struct proto_config *pc;
+  struct bgp_addr_node *an;
+  struct bgp_protocol_cfg *bgp_cfg;
+  node *n;
 
   WALK_LIST(pc, config->protos)
     if (pc->protocol == &proto_bgp && pc->proto)
       {
 	struct bgp_proto *p = (struct bgp_proto *) pc->proto;
+	DBG("Checking proto %s [%I%%%s] vs %I%%%s\n",
+	  p->p.name, p->cf->remote_ip, p->cf->iface? p->cf->iface->name : "NULL",
+	  sk->daddr, sk->iface ? sk->iface->name : "NULL");
 	if (ipa_equal(p->cf->remote_ip, sk->daddr) &&
 	    (!ipa_has_link_scope(sk->daddr) || (p->cf->iface == sk->iface)))
 		return p;
       }
+  bgp_cfg = config->bgp_cfg;
+  if (!bgp_cfg)
+    {
+      DBG("No dynamic templates\n");
+      return NULL;
+    }
+  DBG("Searching for %I in dynamic templates masks\n", sk->daddr);
+  /* Next stage: check dynamic masks */
+  if (ipa_has_link_scope(sk->daddr))
+    {
+      WALK_LIST(n, bgp_cfg->lladdr_list)
+       {
+	   an = SKIP_BACK(struct bgp_addr_node, addr_node, n);
+	   if (!ipa_in_net(sk->daddr, an->tn.addr, an->tn.plen))
+	     continue;
+	   if (an->c->iface != sk->iface)
+	     continue;
+	   return bgp_create_proto(an->c, sk);
+       }
+    }
+  else
+    {
+      an = (struct bgp_addr_node *)trie_match_longest_prefix(bgp_cfg->addr_trie,
+       sk->daddr, MAX_PREFIX_LENGTH);
+      DBG("Searching for %I in address trie\n", sk->daddr);
+      if (an)
+       return bgp_create_proto(an->c, sk);
+    }
 
   return NULL;
 }
@@ -1204,6 +1489,14 @@ bgp_check_config(struct bgp_config *c)
 
   if (c->secondary && !c->c.table->sorted)
     cf_error("BGP with secondary option requires sorted table");
+
+  if (c->c.class == SYM_DYNTEMPLATE)
+    {
+      if (!c->dyn_nameformat)
+       cf_error("Name template has to be specified");
+      if (EMPTY_LIST(c->dyn_allow_list))
+       cf_error("At least one prefix mask has to be specified");
+    }
 }
 
 static int
@@ -1239,6 +1532,8 @@ bgp_copy_config(struct proto_config *dest, struct proto_config *src)
 {
   /* Just a shallow copy */
   proto_copy_rest(dest, src, sizeof(struct bgp_config));
+  /* Do not copy dynamic allow lists */
+  init_list(&((struct bgp_config *)dest)->dyn_allow_list);
 }
 
 
@@ -1437,6 +1732,8 @@ struct protocol proto_bgp = {
   cleanup:		bgp_cleanup,
   reconfigure:		bgp_reconfigure,
   copy_config:		bgp_copy_config,
+  copy_dynconfig:	bgp_copy_dynconfig,
+  retest_dynconfig:	bgp_retest_dynconfig,
   get_status:		bgp_get_status,
   get_attr:		bgp_get_attr,
   get_route_info:	bgp_get_route_info,
diff --git a/proto/bgp/bgp.h b/proto/bgp/bgp.h
index 0fd3a73..c8487b2 100644
--- a/proto/bgp/bgp.h
+++ b/proto/bgp/bgp.h
@@ -63,6 +63,14 @@ struct bgp_config {
   char *password;			/* Password used for MD5 authentication */
   struct rtable_config *igp_table;	/* Table used for recursive next hop lookups */
   int bfd;				/* Use BFD for liveness detection */
+  int dyn_expire;			/* Min time to expire inactive dynamic protocols */
+  char *dyn_nameformat;			/* Format string to name devived protocols */
+  list dyn_allow_list;			/* list of prefixes allowed to connect */
+};
+
+struct bgp_protocol_cfg {
+  struct f_trie *addr_trie;		/* trie for global prefixes */
+  list lladdr_list;			/* list to store link-local addresses */
 };
 
 #define MLL_SELF 1
@@ -197,6 +205,7 @@ void bgp_handle_graceful_restart(struct bgp_proto *p);
 void bgp_graceful_restart_done(struct bgp_proto *p);
 void bgp_store_error(struct bgp_proto *p, struct bgp_conn *c, u8 class, u32 code);
 void bgp_stop(struct bgp_proto *p, unsigned subcode);
+void bgp_add_allow_mask(struct config *new, struct bgp_config *c, ip_addr addr, int plen);
 
 struct rte_source *bgp_find_source(struct bgp_proto *p, u32 path_id);
 struct rte_source *bgp_get_source(struct bgp_proto *p, u32 path_id);
diff --git a/proto/bgp/config.Y b/proto/bgp/config.Y
index d0ae064..4c83ba1 100644
--- a/proto/bgp/config.Y
+++ b/proto/bgp/config.Y
@@ -26,7 +26,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY,
 	PREFER, OLDER, MISSING, LLADDR, DROP, IGNORE, ROUTE, REFRESH,
 	INTERPRET, COMMUNITIES, BGP_ORIGINATOR_ID, BGP_CLUSTER_LIST, IGP,
 	TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC,
-	SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE)
+	SECONDARY, ALLOW, BFD, ADD, PATHS, RX, TX, GRACEFUL, RESTART, AWARE,
+	EXPIRE, INACTIVE)
 
 CF_GRAMMAR
 
@@ -52,6 +53,7 @@ bgp_proto_start: proto_start BGP {
      BGP_CFG->default_local_pref = 100;
      BGP_CFG->gr_mode = BGP_GR_AWARE;
      BGP_CFG->gr_time = 120;
+     init_list(&BGP_CFG->dyn_allow_list);
  }
  ;
 
@@ -124,6 +126,17 @@ bgp_proto:
  | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; }
  | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; }
  | bgp_proto BFD bool ';' { BGP_CFG->bfd = $3; cf_check_bfd($3); }
+ | bgp_proto DYNAMIC ALLOW bgp_allow_dynamic ';'
+ | bgp_proto DYNAMIC TEMPLATE NAME text ';' { BGP_CFG->dyn_nameformat = $5; }
+ | bgp_proto DYNAMIC EXPIRE INACTIVE expr ';' { BGP_CFG->dyn_expire = $5; }
+ ;
+
+bgp_allow_dynamic:
+  bgp_allow_dynamic_entry
+ | bgp_allow_dynamic ',' bgp_allow_dynamic_entry
+ ;
+
+bgp_allow_dynamic_entry: prefix_or_ipa { bgp_add_allow_mask(new_config, BGP_CFG, $1.addr, $1.len); }
  ;
 
 CF_ADDTO(dynamic_attr, BGP_ORIGIN
-- 
2.1.2

