]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Implement RA_ACCEPTED mode of route propagation.
authorOndrej Zajicek <santiago@crfreenet.org>
Sun, 15 Apr 2012 13:07:58 +0000 (15:07 +0200)
committerOndrej Zajicek <santiago@crfreenet.org>
Sun, 15 Apr 2012 13:07:58 +0000 (15:07 +0200)
nest/route.h
nest/rt-table.c
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y

index 345366097132b9985692c90e2f1627f7f084c23c..59daf80303d55bb9e332e38c8f011bceeea7ac9d 100644 (file)
@@ -219,11 +219,12 @@ typedef struct rte {
   } u;
 } rte;
 
-#define REF_COW 1                      /* Copy this rte on write */
+#define REF_COW                1               /* Copy this rte on write */
 
 /* Types of route announcement, also used as flags */
-#define RA_OPTIMAL 1                   /* Announcement of optimal route change */
-#define RA_ANY 2                       /* Announcement of any route change */
+#define RA_OPTIMAL     1               /* Announcement of optimal route change */
+#define RA_ACCEPTED    2               /* Announcement of first accepted route */
+#define RA_ANY         3               /* Announcement of any route change */
 
 struct config;
 
index 310c1afdadf0b4f8acc6334516d281c7669868db..578a6ba3afa1fdad8625187e781f2b8ddc048300 100644 (file)
@@ -182,86 +182,73 @@ rte_trace_out(unsigned int flag, struct proto *p, rte *e, char *msg)
     rte_trace(p, e, '<', msg);
 }
 
-static inline void
-do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, rte *old, ea_list *tmpa, int refeed)
+static rte *
+export_filter(struct announce_hook *ah, rte *rt0, rte **rt_free, ea_list **tmpa, int silent)
 {
   struct proto *p = ah->proto;
   struct filter *filter = ah->out_filter;
   struct proto_stats *stats = ah->stats;
+  ea_list *tmpb = NULL;
+  rte *rt;
+  int v;
 
-  rte *new0 = new;
-  rte *old0 = old;
-  int ok;
+  rt = rt0;
+  *rt_free = NULL;
 
-  if (new)
+  /* If called does not care for eattrs, we prepare one internally */
+  if (!tmpa)
     {
-      stats->exp_updates_received++;
-
-      char *drop_reason = NULL;
-      if ((ok = p->import_control ? p->import_control(p, &new, &tmpa, rte_update_pool) : 0) < 0)
-       {
-         stats->exp_updates_rejected++;
-         drop_reason = "rejected by protocol";
-       }
-      else if (ok)
-       rte_trace_out(D_FILTERS, p, new, "forced accept by protocol");
-      else if ((filter == FILTER_REJECT) ||
-              (filter && f_run(filter, &new, &tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT))
-       {
-         stats->exp_updates_filtered++;
-         drop_reason = "filtered out";
-       }
-      if (drop_reason)
-       {
-         rte_trace_out(D_FILTERS, p, new, drop_reason);
-         if (new != new0)
-           rte_free(new);
-         new = NULL;
-       }
+      struct proto *src = rt->attrs->proto;
+      tmpb = src->make_tmp_attrs ? src->make_tmp_attrs(rt, rte_update_pool) : NULL;
+      tmpa = &tmpb;
     }
-  else
-    stats->exp_withdraws_received++;
 
-  /*
-   * This is a tricky part - we don't know whether route 'old' was
-   * exported to protocol 'p' or was filtered by the export filter.
-   * We try tu run the export filter to know this to have a correct
-   * value in 'old' argument of rte_update (and proper filter value)
-   *
-   * FIXME - this is broken because 'configure soft' may change
-   * filters but keep routes. Refeed is expected to be called after
-   * change of the filters and with old == new, therefore we do not
-   * even try to run the filter on an old route, This may lead to 
-   * 'spurious withdraws' but ensure that there are no 'missing
-   * withdraws'.
-   *
-   * This is not completely safe as there is a window between
-   * reconfiguration and the end of refeed - if a newly filtered
-   * route disappears during this period, proper withdraw is not
-   * sent (because old would be also filtered) and the route is
-   * not refeeded (because it disappeared before that).
-   */
+  v = p->import_control ? p->import_control(p, &rt, tmpa, rte_update_pool) : 0;
+  if (v < 0)
+    {
+      if (silent)
+       goto reject;
 
-  if (old && !refeed)
+      stats->exp_updates_rejected++;
+      rte_trace_out(D_FILTERS, p, rt, "rejected by protocol");
+      goto reject;
+    }
+  if (v > 0)
     {
-      if (filter == FILTER_REJECT)
-       old = NULL;
-      else
-       {
-         ea_list *tmpb = p->make_tmp_attrs ? p->make_tmp_attrs(old, rte_update_pool) : NULL;
-         ok = p->import_control ? p->import_control(p, &old, &tmpb, rte_update_pool) : 0;
-         if (ok < 0 || (!ok && filter && f_run(filter, &old, &tmpb, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT))
-           {
-             if (old != old0)
-               rte_free(old);
-             old = NULL;
-           }
-       }
+      if (!silent)
+       rte_trace_out(D_FILTERS, p, rt, "forced accept by protocol");
+      goto accept;
     }
 
-  /* FIXME - This is broken because of incorrect 'old' value (see above) */
-  if (!new && !old)
-    return;
+  v = filter && ((filter == FILTER_REJECT) ||
+                (f_run(filter, &rt, tmpa, rte_update_pool, FF_FORCE_TMPATTR) > F_ACCEPT));
+  if (v)
+    {
+      if (silent)
+       goto reject;
+
+      stats->exp_updates_filtered++;
+      rte_trace_out(D_FILTERS, p, rt, "filtered out");
+      goto reject;
+    }
+
+ accept:
+  if (rt != rt0)
+    *rt_free = rt;
+  return rt;
+
+ reject:
+  /* Discard temporary rte */
+  if (rt != rt0)
+    rte_free(rt);
+  return NULL;
+}
+
+static void
+do_rt_notify(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed)
+{
+  struct proto *p = ah->proto;
+  struct proto_stats *stats = ah->stats;
 
   if (new)
     stats->exp_updates_accepted++;
@@ -297,10 +284,174 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r
     }
   else
     p->rt_notify(p, ah->table, net, new, old, new->attrs->eattrs);
-  if (new && new != new0)      /* Discard temporary rte's */
-    rte_free(new);
-  if (old && old != old0)
-    rte_free(old);
+
+}
+
+
+static void
+rt_notify_basic(struct announce_hook *ah, net *net, rte *new, rte *old, ea_list *tmpa, int refeed)
+{
+  // struct proto *p = ah->proto;
+  struct proto_stats *stats = ah->stats;
+
+  rte *new_free = NULL;
+  rte *old_free = NULL;
+
+  if (new)
+    stats->exp_updates_received++;
+  else
+    stats->exp_withdraws_received++;
+
+  /*
+   * This is a tricky part - we don't know whether route 'old' was
+   * exported to protocol 'p' or was filtered by the export filter.
+   * We try to run the export filter to know this to have a correct
+   * value in 'old' argument of rte_update (and proper filter value)
+   *
+   * FIXME - this is broken because 'configure soft' may change
+   * filters but keep routes. Refeed is expected to be called after
+   * change of the filters and with old == new, therefore we do not
+   * even try to run the filter on an old route, This may lead to 
+   * 'spurious withdraws' but ensure that there are no 'missing
+   * withdraws'.
+   *
+   * This is not completely safe as there is a window between
+   * reconfiguration and the end of refeed - if a newly filtered
+   * route disappears during this period, proper withdraw is not
+   * sent (because old would be also filtered) and the route is
+   * not refeeded (because it disappeared before that).
+   */
+
+  if (new)
+    new = export_filter(ah, new, &new_free, &tmpa, 0);
+
+  if (old && !refeed)
+    old = export_filter(ah, old, &old_free, NULL, 1);
+
+  /* FIXME - This is broken because of incorrect 'old' value (see above) */
+  if (!new && !old)
+    return;
+
+  do_rt_notify(ah, net, new, old, tmpa, refeed);
+
+  /* Discard temporary rte's */
+  if (new_free)
+    rte_free(new_free);
+  if (old_free)
+    rte_free(old_free);
+}
+
+static void
+rt_notify_accepted(struct announce_hook *ah, net *net, rte *new_changed, rte *old_changed, rte *before_old,
+                  ea_list *tmpa, int feed)
+{
+  // struct proto *p = ah->proto;
+  struct proto_stats *stats = ah->stats;
+
+  rte *new_best = NULL;
+  rte *old_best = NULL;
+  rte *new_free = NULL;
+  rte *old_free = NULL;
+  rte *r;
+
+  /* Used to track whethe we met old_changed position. If it is NULL
+     it was the first and met it implicitly before current best route. */
+  int old_meet = (old_changed && !before_old) ? 1 : 0;
+
+  if (new_changed)
+    stats->exp_updates_received++;
+  else
+    stats->exp_withdraws_received++;
+
+  /* First, find the new_best route - first accepted by filters */
+  for (r=net->routes; r; r=r->next)
+    {
+      if (new_best = export_filter(ah, r, &new_free, &tmpa, 0))
+       break;
+
+      /* Note if we walked around the position of old_changed route */
+      if (r == before_old)
+       old_meet = 1;
+    }
+
+  /* 
+   * Second, handle the feed case. That means we do not care for
+   * old_best. It is NULL for feed, and the new_best for refeed. 
+   * For refeed, there is a hack similar to one in rt_notify_basic()
+   * to ensure withdraws in case of changed filters
+   */
+  if (feed)
+    {
+      if (feed == 2)   /* refeed */
+       old_best = new_best ? new_best : net->routes;
+      else
+       old_best = NULL;
+
+      if (!new_best && !old_best)
+       return;
+
+      goto found;
+    }
+
+  /*
+   * Now, we find the old_best route. Generally, it is the same as the
+   * new_best, unless new_best is the same as new_changed or
+   * old_changed is accepted before new_best.
+   *
+   * There are four cases:
+   *
+   * - We would find and accept old_changed before new_best, therefore
+   *   old_changed is old_best. In remaining cases we suppose this
+   *   is not true.
+   *
+   * - We found no new_best, therefore there is also no old_best and
+   *   we ignore this withdraw.
+   *
+   * - We found new_best different than new_changed, therefore
+   *   old_best is the same as new_best and we ignore this update.
+   *
+   * - We found new_best the same as new_changed, therefore it cannot
+   *   be old_best and we have to continue search for old_best.
+   */
+
+  /* First case */
+  if (old_meet)
+    if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1))
+      goto found;
+
+  /* Second case */
+  if (!new_best)
+    return;
+
+  /* Third case, we use r insead of new_best, because export_filter() could change it */
+  if (r != new_changed)
+    {
+      if (new_free)
+       rte_free(new_free);
+      return;
+    }
+
+  /* Fourth case */
+  for (; r; r=r->next)
+    {
+      if (old_best = export_filter(ah, r, &old_free, NULL, 1))
+       goto found;
+
+      if (r == before_old)
+       if (old_best = export_filter(ah, old_changed, &old_free, NULL, 1))
+         goto found;
+    }
+
+  /* Implicitly, old_best is NULL and new_best is non-NULL */
+
+ found:
+  do_rt_notify(ah, net, new_best, old_best, tmpa, (feed == 2));
+
+  /* Discard temporary rte's */
+  if (new_free)
+    rte_free(new_free);
+  if (old_free)
+    rte_free(old_free);
 }
 
 /**
@@ -333,7 +484,7 @@ do_rte_announce(struct announce_hook *ah, int type UNUSED, net *net, rte *new, r
  * the protocol gets called.
  */
 static void
-rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list *tmpa)
+rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, rte *before_old, ea_list *tmpa)
 {
   struct announce_hook *a;
 
@@ -352,11 +503,13 @@ rte_announce(rtable *tab, unsigned type, net *net, rte *new, rte *old, ea_list *
     {
       ASSERT(a->proto->core_state == FS_HAPPY || a->proto->core_state == FS_FEEDING);
       if (a->proto->accept_ra_types == type)
-       do_rte_announce(a, type, net, new, old, tmpa, 0);
+       if (type == RA_ACCEPTED)
+         rt_notify_accepted(a, net, new, old, before_old, tmpa, 0);
+       else
+         rt_notify_basic(a, net, new, old, tmpa, 0);
     }
 }
 
-
 static inline int
 rte_validate(rte *e)
 {
@@ -419,9 +572,10 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
   struct proto *p = ah->proto;
   struct rtable *table = ah->table;
   struct proto_stats *stats = ah->stats;
+  rte *before_old = NULL;
   rte *old_best = net->routes;
   rte *old = NULL;
-  rte **k, *r, *s;
+  rte **k;
 
   k = &net->routes;                    /* Find and remove original route from the same protocol */
   while (old = *k)
@@ -466,8 +620,12 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
          break;
        }
       k = &old->next;
+      before_old = old;
     }
 
+  if (!old)
+    before_old = NULL;
+
   if (!old && !new)
     {
       stats->imp_withdraws_ignored++;
@@ -484,82 +642,21 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
   if (old)
     stats->imp_routes--;
 
-  rte_announce(table, RA_ANY, net, new, old, tmpa);
-
-  if (src->rte_recalculate && src->rte_recalculate(table, net, new, old, old_best))
-    goto do_recalculate;
-
-  if (new && rte_better(new, old_best))
-    {
-      /* The first case - the new route is cleary optimal, we link it
-        at the first position and announce it */
-
-      rte_trace_in(D_ROUTES, p, new, "added [best]");
-      rte_announce(table, RA_OPTIMAL, net, new, old_best, tmpa);
-      new->next = net->routes;
-      net->routes = new;
-    }
-  else if (old == old_best)
+  if (new)
     {
-      /* The second case - the old best route disappeared, we add the
-        new route (if we have any) to the list (we don't care about
-        position) and then we elect the new optimal route and relink
-        that route at the first position and announce it. New optimal
-        route might be NULL if there is no more routes */
-
-    do_recalculate:
-      /* Add the new route to the list */
-      if (new)
-       {
-         rte_trace_in(D_ROUTES, p, new, "added");
-         new->next = net->routes;
-         net->routes = new;
-       }
-
-      /* Find new optimal route */
-      r = NULL;
-      for (s=net->routes; s; s=s->next)
-       if (rte_better(s, r))
-         r = s;
+      for (k=&net->routes; *k; k=&(*k)->next)
+       if (rte_better(new, *k))
+         break;
 
-      /* Announce optimal route */
-      rte_announce(table, RA_OPTIMAL, net, r, old_best, tmpa);
+      new->lastmod = now;
+      new->next = *k;
+      *k = new;
 
-      /* And relink it (if there is any) */
-      if (r)
-       {
-         k = &net->routes;
-         while (s = *k)
-           {
-             if (s == r)
-               {
-                 *k = r->next;
-                 break;
-               }
-             k = &s->next;
-           }
-         r->next = net->routes;
-         net->routes = r;
-       }
-      else if (table->gc_counter++ >= table->config->gc_max_ops &&
-              table->gc_time + table->config->gc_min_time <= now)
-       rt_schedule_gc(table);
-    }
-  else if (new)
-    {
-      /* The third case - the new route is not better than the old
-        best route (therefore old_best != NULL) and the old best
-        route was not removed (therefore old_best == net->routes).
-        We just link the new route after the old best route. */
-
-      ASSERT(net->routes != NULL);
-      new->next = net->routes->next;
-      net->routes->next = new;
-      rte_trace_in(D_ROUTES, p, new, "added");
+      rte_trace_in(D_ROUTES, p, new, net->routes == new ? "added [best]" : "added");
     }
 
   /* Log the route removal */
-  if (!new && old && (p->debug & D_ROUTES))
+  if (!new && (p->debug & D_ROUTES))
     {
       if (old != old_best)
        rte_trace_in(D_ROUTES, p, old, "removed");
@@ -569,6 +666,15 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
        rte_trace_in(D_ROUTES, p, old, "removed [sole]");
     }
 
+  rte_announce(table, RA_ANY, net, new, old, NULL, tmpa);
+  rte_announce(table, RA_OPTIMAL, net, net->routes, old_best, NULL, tmpa);
+  rte_announce(table, RA_ACCEPTED, net, new, old, before_old, tmpa);
+
+  if (!net->routes &&
+      (table->gc_counter++ >= table->config->gc_max_ops) &&
+      (table->gc_time + table->config->gc_min_time <= now))
+    rt_schedule_gc(table);
+
   if (old)
     {
       if (p->rte_remove)
@@ -577,7 +683,6 @@ rte_recalculate(struct announce_hook *ah, net *net, rte *new, ea_list *tmpa, str
     }
   if (new)
     {
-      new->lastmod = now;
       if (p->rte_insert)
        p->rte_insert(net, new);
     }
@@ -709,7 +814,7 @@ rte_announce_i(rtable *tab, unsigned type, net *n, rte *new, rte *old)
   rte_update_lock();
   src = new->attrs->proto;
   tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(new, rte_update_pool) : NULL;
-  rte_announce(tab, type, n, new, old, tmpa);
+  rte_announce(tab, type, n, new, old, NULL, tmpa);
   rte_update_unlock();
 }
 
@@ -1259,12 +1364,15 @@ rt_commit(struct config *new, struct config *old)
 static inline void
 do_feed_baby(struct proto *p, int type, struct announce_hook *h, net *n, rte *e)
 {
-  struct proto *q = e->attrs->proto;
+  struct proto *src = e->attrs->proto;
   ea_list *tmpa;
 
   rte_update_lock();
-  tmpa = q->make_tmp_attrs ? q->make_tmp_attrs(e, rte_update_pool) : NULL;
-  do_rte_announce(h, type, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding);
+  tmpa = src->make_tmp_attrs ? src->make_tmp_attrs(e, rte_update_pool) : NULL;
+  if (type == RA_ACCEPTED)
+    rt_notify_accepted(h, n, e, NULL, NULL, tmpa, p->refeeding ? 2 : 1);
+  else
+    rt_notify_basic(h, n, e, p->refeeding ? e : NULL, tmpa, p->refeeding);
   rte_update_unlock();
 }
 
index 4dd4b7be2054a8a671b011102504457112abbe88..099a39a9897b0589e9606bc5e791392239d1c237 100644 (file)
@@ -909,7 +909,7 @@ bgp_init(struct proto_config *C)
   struct proto *P = proto_new(C, sizeof(struct bgp_proto));
   struct bgp_proto *p = (struct bgp_proto *) P;
 
-  P->accept_ra_types = RA_OPTIMAL;
+  P->accept_ra_types = c->secondary ? RA_ACCEPTED : RA_OPTIMAL;
   P->rt_notify = bgp_rt_notify;
   P->rte_better = bgp_rte_better;
   P->import_control = bgp_import_control;
@@ -955,12 +955,14 @@ bgp_check_config(struct bgp_config *c)
   if (internal && c->rs_client)
     cf_error("Only external neighbor can be RS client");
 
+  /*
   if (c->multihop && (c->gw_mode == GW_DIRECT))
     cf_error("Multihop BGP cannot use direct gateway mode");
 
   if (c->multihop && (ipa_has_link_scope(c->remote_ip) || 
                      ipa_has_link_scope(c->source_addr)))
     cf_error("Multihop BGP cannot be used with link-local addresses");
+  */
 
   /* Different default based on rs_client */
   if (!c->missing_lladdr)
@@ -968,7 +970,8 @@ bgp_check_config(struct bgp_config *c)
 
   /* Different default for gw_mode */
   if (!c->gw_mode)
-    c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT;
+    // c->gw_mode = (c->multihop || internal) ? GW_RECURSIVE : GW_DIRECT;
+    c->gw_mode = GW_DIRECT;
 }
 
 static int
index a8c5818e517a07b88bb1c54770b2ba810d4a25ef..87734425caf7c93c04554e2a29df14ef6b8682f4 100644 (file)
@@ -43,6 +43,7 @@ struct bgp_config {
   u32 route_limit;                     /* Number of routes that may be imported, 0 means disable limit */
   int passive;                         /* Do not initiate outgoing connection */
   int interpret_communities;           /* Hardwired handling of well-known communities */
+  int secondary;                       /* Accept also non-best routes (i.e. RA_ACCEPTED) */
   unsigned connect_retry_time;
   unsigned hold_time, initial_hold_time;
   unsigned keepalive_time;
index 78ca52de6ad426f98566837b2f95512ac66f9b5c..356415a24ec375b7eb38ee7a1f2f38a9a20331cc 100644 (file)
@@ -25,7 +25,8 @@ CF_KEYWORDS(BGP, LOCAL, NEIGHBOR, AS, HOLD, TIME, CONNECT, RETRY,
        CLUSTER, ID, AS4, ADVERTISE, IPV4, CAPABILITIES, LIMIT, PASSIVE,
        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)
+       TABLE, GATEWAY, DIRECT, RECURSIVE, MED, TTL, SECURITY, DETERMINISTIC,
+       SECONDARY)
 
 CF_GRAMMAR
 
@@ -73,19 +74,25 @@ bgp_proto:
  | bgp_proto STARTUP HOLD TIME expr ';' { BGP_CFG->initial_hold_time = $5; }
  | bgp_proto CONNECT RETRY TIME expr ';' { BGP_CFG->connect_retry_time = $5; }
  | bgp_proto KEEPALIVE TIME expr ';' { BGP_CFG->keepalive_time = $4; }
+/*
  | bgp_proto MULTIHOP ';' { BGP_CFG->multihop = 64; }
  | bgp_proto MULTIHOP expr ';' { BGP_CFG->multihop = $3; if (($3<1) || ($3>255)) cf_error("Multihop must be in range 1-255"); }
+*/
  | bgp_proto NEXT HOP SELF ';' { BGP_CFG->next_hop_self = 1; }
  | bgp_proto MISSING LLADDR SELF ';' { BGP_CFG->missing_lladdr = MLL_SELF; }
  | bgp_proto MISSING LLADDR DROP ';' { BGP_CFG->missing_lladdr = MLL_DROP; }
  | bgp_proto MISSING LLADDR IGNORE ';' { BGP_CFG->missing_lladdr = MLL_IGNORE; }
+/*
  | bgp_proto GATEWAY DIRECT ';' { BGP_CFG->gw_mode = GW_DIRECT; }
  | bgp_proto GATEWAY RECURSIVE ';' { BGP_CFG->gw_mode = GW_RECURSIVE; }
+*/
  | bgp_proto PATH METRIC bool ';' { BGP_CFG->compare_path_lengths = $4; }
  | bgp_proto MED METRIC bool ';' { BGP_CFG->med_metric = $4; }
  | bgp_proto IGP METRIC bool ';' { BGP_CFG->igp_metric = $4; }
  | bgp_proto PREFER OLDER bool ';' { BGP_CFG->prefer_older = $4; }
+/*
  | bgp_proto DETERMINISTIC MED bool ';' { BGP_CFG->deterministic_med = $4; }
+*/
  | bgp_proto DEFAULT BGP_MED expr ';' { BGP_CFG->default_med = $4; }
  | bgp_proto DEFAULT BGP_LOCAL_PREF expr ';' { BGP_CFG->default_local_pref = $4; }
  | bgp_proto SOURCE ADDRESS ipa ';' { BGP_CFG->source_addr = $4; }
@@ -101,6 +108,7 @@ bgp_proto:
  | bgp_proto ROUTE LIMIT expr ';' { BGP_CFG->route_limit = $4; }
  | bgp_proto PASSIVE bool ';' { BGP_CFG->passive = $3; }
  | bgp_proto INTERPRET COMMUNITIES bool ';' { BGP_CFG->interpret_communities = $4; }
+ | bgp_proto SECONDARY bool ';' { BGP_CFG->secondary = $3; }
  | bgp_proto IGP TABLE rtable ';' { BGP_CFG->igp_table = $4; }
  | bgp_proto TTL SECURITY bool ';' { BGP_CFG->ttl_security = $4; }
  ;