From 7d8303ea3210156ed797f23423ed1e53ad365398 Mon Sep 17 00:00:00 2001
From: "Alexander V. Chernikov" <melifaro@ipfw.ru>
Date: Sun, 15 Feb 2015 14:24:02 +0300
Subject: [PATCH 8/8] Implement dynamic BGP protocols creation based on
 incoming connections.

---
 conf/conf.h        |   3 +
 proto/bgp/bgp.c    | 374 +++++++++++++++++++++++++++++++++++++++++++++++++++++
 proto/bgp/bgp.h    |   9 ++
 proto/bgp/config.Y |  15 ++-
 4 files changed, 400 insertions(+), 1 deletion(-)

diff --git a/conf/conf.h b/conf/conf.h
index 9490377..334d31c 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. */
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 0fd2b68..a6bf7f4 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,40 @@ 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 */
+};
+
+/* Used to pass data to dynamic reconfigure callback */
+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
@@ -713,6 +748,271 @@ bgp_connect(struct bgp_proto *p)	/* Enter Connect state and start establishing c
 }
 
 /**
+ * bgp_print_template - creates proto named based on template
+ * @buf - pointer to buffer storage
+ * @size - size of supplied buffer
+ * @fmt - format string
+ * @tname - protocol table name
+ * @pi - additional data used to create new proto
+ *
+ * Returns new protocol name length or -1
+ */
+static int
+bgp_print_template(char *buf, int size, const char *fmt, const char *tname, struct bgp_dtemplate_pinfo *pi)
+{
+  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 = pi->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(pi->addr);
+	  a = (a >> off) & 0xFF;
+	  len = bsnprintf(ipbuf, sizeof(ipbuf), "%u", a);
+	  buf += len;
+	  size -= len;
+#endif
+	  break;
+
+	/* IP address */
+	case 'I':
+	  ip_ntop(pi->addr, ipbuf);
+	  len = strlen(ipbuf);
+	  break;
+
+	/* Table name */
+	case 'T':
+	  len = bsnprintf(ipbuf, sizeof(ipbuf), "%s", tname);
+	  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;
+  struct rtable_config *t;
+  char pname[SYM_MAX_LEN];
+
+  /* Use master table in the template by defaul. */
+  t = tc->c.table;
+  pi.addr = sk->daddr;
+
+  if (bgp_print_template(pname, sizeof(pname), tc->dyn_nameformat, t->name, &pi) == -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);
+
+
+  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 = NULL;
+
+  bgp_cfg = bgp_get_protocol_config(new);
+
+  /* Handle link-local addresses differently */
+  if (ipa_has_link_scope(addr) || c->c.class != SYM_DYNTEMPLATE)
+    {
+      /* LL or non-dynamic template. Do not attach to global trie */
+      an = (struct bgp_addr_node *)cfl_allocz(new, sizeof(struct bgp_addr_node));
+      an->tn.addr = addr;
+      an->tn.plen = plen;
+    }
+  else
+    {
+      /*
+       * Global address AND dynamic template.
+       * We should get here only in case of real reconfiguration
+       */
+      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);
+    }
+
+  if (ipa_has_link_scope(addr) && c->c.class == SYM_DYNTEMPLATE)
+    add_tail(&bgp_cfg->lladdr_list, &an->addr_node);
+
+  an->c = c;
+  add_tail(&c->dyn_allow_list, &an->allow_node);
+
+}
+
+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;
+    }
+
+  if (strcmp(oc->dyn_nameformat, nc->dyn_nameformat))
+    {
+      log(L_ERR "Name format changed for dynamic proto %s: %s vs %s",
+       p->name, oc->dyn_nameformat, nc->dyn_nameformat);
+      return 0;
+    }
+
+  if (strcmp(oc->dyn_nameformat, nc->dyn_nameformat))
+    {
+      log(L_ERR "Name format changed for dynamic proto %s: %s vs %s",
+       p->name, oc->dyn_nameformat, nc->dyn_nameformat);
+      return 0;
+    }
+
+  if (p->disabled)
+    {
+      log(L_ERR "Removing disabled dynamic proto %s", p->name);
+      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,6 +1021,12 @@ 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;
+
+  if (config->shutdown)
+    return NULL;
 
   WALK_LIST(pc, config->protos)
     if (pc->protocol == &proto_bgp && pc->proto)
@@ -730,6 +1036,34 @@ bgp_find_proto(sock *sk)
 	    (!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 +1538,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
@@ -1237,8 +1579,38 @@ bgp_reconfigure(struct proto *P, struct proto_config *C)
 static void
 bgp_copy_config(struct proto_config *dest, struct proto_config *src)
 {
+  struct bgp_config *nc, *oc;
+  struct bgp_addr_node *an, *xn;
+  node *n;
+
   /* Just a shallow copy */
   proto_copy_rest(dest, src, sizeof(struct bgp_config));
+
+  nc = (struct bgp_config *)dest;
+  oc = (struct bgp_config *)src;
+  /* Do not blindly copy dynamic allow lists */
+  init_list(&nc->dyn_allow_list);
+  if (dest->class == SYM_TEMPLATE)
+    {
+      /* copy prefix info only to permit dynamic template inheritance */
+      WALK_LIST(n, oc->dyn_allow_list)
+	{
+	  an = SKIP_BACK(struct bgp_addr_node, allow_node, n);
+	  xn = cfl_allocz(dest->global, sizeof(*xn));
+	  xn->tn.addr = an->tn.addr;
+	  xn->tn.plen = an->tn.plen;
+	  add_tail(&nc->dyn_allow_list, &xn->allow_node);
+	}
+    }
+  else if (dest->class == SYM_DYNTEMPLATE)
+    {
+      /* copy with saving to global trie */
+      WALK_LIST(n, oc->dyn_allow_list)
+	{
+	  an = SKIP_BACK(struct bgp_addr_node, allow_node, n);
+	  bgp_add_allow_mask(dest->global, nc, an->tn.addr, an->tn.plen);
+	}
+    }
 }
 
 
@@ -1437,6 +1809,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

