]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge master into int-new
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Fri, 28 Apr 2017 09:19:12 +0000 (11:19 +0200)
committerOndrej Zajicek (work) <santiago@crfreenet.org>
Fri, 28 Apr 2017 09:19:12 +0000 (11:19 +0200)
1  2 
doc/bird.sgml
proto/bgp/attrs.c
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/config.Y

diff --cc doc/bird.sgml
Simple merge
index cf9db1c8f0f93882d1599243697e857affa470bb,b9e2490dc65cfc10b3c82e47bc4ee0682bdafa2a..882ba44ea1ba4c53039bc3c195413225941a803d
@@@ -179,210 -94,159 +179,210 @@@ bgp_encode_u32(struct bgp_write_state *
  }
  
  static int
 -path_segment_contains(byte *p, int bs, u32 asn)
 +bgp_encode_u32s(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
  {
 -  int i;
 -  int len = p[1];
 -  p += 2;
 +  uint len = a->u.ptr->length;
  
 -  for(i=0; i<len; i++)
 -    {
 -      u32 asn2 = (bs == 4) ? get_u32(p) : get_u16(p);
 -      if (asn2 == asn)
 -      return 1;
 -      p += bs;
 -    }
 +  if (size < (4+len))
 +    return -1;
  
 -  return 0;
 +  uint hdr = bgp_put_attr_hdr(buf, EA_ID(a->id), a->flags, len);
 +  put_u32s(buf + hdr, (u32 *) a->u.ptr->data, len / 4);
 +
 +  return hdr + len;
  }
  
 -/* Validates path attribute, removes AS_CONFED_* segments, and also returns path length */
  static int
 -validate_path(struct bgp_proto *p, int as_path, int bs, byte *idata, uint *ilength)
 +bgp_put_attr(byte *buf, uint size, uint code, uint flags, byte *data, uint len)
  {
 -  int res = 0;
 -  u8 *a, *dst;
 -  int len, plen;
 +  if (size < (4+len))
 +    return -1;
  
 -  dst = a = idata;
 -  len = *ilength;
 +  uint hdr = bgp_put_attr_hdr(buf, code, flags, len);
 +  memcpy(buf + hdr, data, len);
  
 -  while (len)
 -    {
 -      if (len < 2)
 -      return -1;
 -
 -      plen = 2 + bs * a[1];
 -      if (len < plen)
 -      return -1;
 -
 -      if (a[1] == 0)
 -        {
 -        log(L_WARN "%s: %s_PATH attribute contains empty segment, skipping it",
 -            p->p.name, as_path ? "AS" : "AS4");
 -        goto skip;
 -      }
 -
 -      switch (a[0])
 -      {
 -      case AS_PATH_SET:
 -        res++;
 -        break;
 -
 -      case AS_PATH_SEQUENCE:
 -        res += a[1];
 -        break;
 -
 -      case AS_PATH_CONFED_SEQUENCE:
 -      case AS_PATH_CONFED_SET:
 -        if (as_path && path_segment_contains(a, bs, p->remote_as))
 -          {
 -            log(L_WARN "%s: AS_CONFED_* segment with peer ASN found, misconfigured confederation?", p->p.name);
 -            return -1;
 -          }
 -
 -        log(L_WARN "%s: %s_PATH attribute contains AS_CONFED_* segment, skipping segment",
 -            p->p.name, as_path ? "AS" : "AS4");
 -        goto skip;
 -
 -      default:
 -        return -1;
 -      }
 -
 -      if (dst != a)
 -      memmove(dst, a, plen);
 -      dst += plen;
 -
 -    skip:
 -      len -= plen;
 -      a += plen;
 -    }
 +  return hdr + len;
 +}
  
 -  *ilength = dst - idata;
 -  return res;
 +static int
 +bgp_encode_raw(struct bgp_write_state *s UNUSED, eattr *a, byte *buf, uint size)
 +{
 +  return bgp_put_attr(buf, size, EA_ID(a->id), a->flags, a->u.ptr->data, a->u.ptr->length);
  }
  
 -static inline int
 -validate_as_path(struct bgp_proto *p, byte *a, int *len)
 +
 +/*
 + *    Attribute hooks
 + */
 +
 +static void
 +bgp_export_origin(struct bgp_export_state *s, eattr *a)
  {
 -  return validate_path(p, 1, p->as4_session ? 4 : 2, a, len);
 +  if (a->u.data > 2)
 +    WITHDRAW(BAD_VALUE, "ORIGIN", a->u.data);
  }
  
 -static inline int
 -validate_as4_path(struct bgp_proto *p, struct adata *path)
 +static void
 +bgp_decode_origin(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
  {
 -  return validate_path(p, 0, 4, path->data, &path->length);
 +  if (len != 1)
 +    WITHDRAW(BAD_LENGTH, "ORIGIN", len);
 +
 +  if (data[0] > 2)
 +    WITHDRAW(BAD_VALUE, "ORIGIN", data[0]);
 +
 +  bgp_set_attr_u32(to, s->pool, BA_ORIGIN, flags, data[0]);
  }
  
 +static void
 +bgp_format_origin(eattr *a, byte *buf, uint size UNUSED)
 +{
 +  static const char *bgp_origin_names[] = { "IGP", "EGP", "Incomplete" };
 +
 +  bsprintf(buf, (a->u.data <= 2) ? bgp_origin_names[a->u.data] : "?");
 +}
 +
 +
  static int
 -bgp_check_next_hop(struct bgp_proto *p UNUSED, byte *a UNUSED6, int len UNUSED6)
 +bgp_encode_as_path(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
  {
 -#ifdef IPV6
 -  return IGNORE;
 -#else
 -  ip_addr addr;
 +  byte *data = a->u.ptr->data;
 +  uint len = a->u.ptr->length;
  
 -  memcpy(&addr, a, len);
 -  ipa_ntoh(addr);
 -  if (ipa_classify(addr) & IADDR_HOST)
 +  if (!s->as4_session)
 +  {
 +    /* Prepare 16-bit AS_PATH (from 32-bit one) in a temporary buffer */
 +    byte *src = data;
 +    data = alloca(len);
 +    len = as_path_32to16(data, src, len);
 +  }
 +
 +  return bgp_put_attr(buf, size, BA_AS_PATH, a->flags, data, len);
 +}
 +
 +static void
 +bgp_decode_as_path(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
 +{
 +  struct bgp_proto *p = s->proto;
 +  int as_length = s->as4_session ? 4 : 2;
 +  int as_confed = p->cf->confederation && p->is_interior;
 +  char err[128];
 +
 +  if (!as_path_valid(data, len, as_length, as_confed, err, sizeof(err)))
 +    WITHDRAW("Malformed AS_PATH attribute - %s", err);
 +
 +  /* In some circumstances check for initial AS_CONFED_SEQUENCE; RFC 5065 5.0 */
 +  if (p->is_interior && !p->is_internal &&
 +      ((len < 2) || (data[0] != AS_PATH_CONFED_SEQUENCE)))
 +    WITHDRAW("Malformed AS_PATH attribute - %s", "missing initial AS_CONFED_SEQUENCE");
 +
 +  if (!s->as4_session)
 +  {
 +    /* Prepare 32-bit AS_PATH (from 16-bit one) in a temporary buffer */
 +    byte *src = data;
 +    data = alloca(2*len);
 +    len = as_path_16to32(data, src, len);
 +  }
 +
 +  bgp_set_attr_data(to, s->pool, BA_AS_PATH, flags, data, len);
 +}
 +
 +
 +static int
 +bgp_encode_next_hop(struct bgp_write_state *s, eattr *a, byte *buf, uint size)
 +{
 +  /*
 +   * The NEXT_HOP attribute is used only in traditional (IPv4) BGP. In MP-BGP,
 +   * the next hop is encoded as a part of the MP_REACH_NLRI attribute, so we
 +   * store it and encode it later by AFI-specific hooks.
 +   */
 +
 +  if (s->channel->afi == BGP_AF_IPV4)
 +  {
 +    ASSERT(a->u.ptr->length == sizeof(ip_addr));
 +
 +    if (size < (3+4))
 +      return -1;
 +
 +    bgp_put_attr_hdr3(buf, BA_NEXT_HOP, a->flags, 4);
 +    put_ip4(buf+3, ipa_to_ip4( *(ip_addr *) a->u.ptr->data ));
 +
 +    return 3+4;
 +  }
 +  else
 +  {
 +    s->mp_next_hop = a;
      return 0;
 +  }
 +}
 +
 +static void
 +bgp_decode_next_hop(struct bgp_parse_state *s, uint code UNUSED, uint flags UNUSED, byte *data, uint len, ea_list **to UNUSED)
 +{
 +  if (len != 4)
 +    WITHDRAW(BAD_LENGTH, "NEXT_HOP", len);
 +
 +  /* Semantic checks are done later */
 +  s->ip_next_hop_len = len;
 +  s->ip_next_hop_data = data;
 +}
 +
 +/* TODO: This function should use AF-specific hook */
 +static void
 +bgp_format_next_hop(eattr *a, byte *buf, uint size UNUSED)
 +{
 +  ip_addr *nh = (void *) a->u.ptr->data;
 +  uint len = a->u.ptr->length;
 +
 +  ASSERT((len == 16) || (len == 32));
 +
 +  /* in IPv6, we may have two addresses in NEXT HOP */
 +  if ((len == 16) || ipa_zero(nh[1]))
 +    bsprintf(buf, "%I", nh[0]);
    else
 -    return 8;
 -#endif
 +    bsprintf(buf, "%I %I", nh[0], nh[1]);
  }
  
 +
  static void
 -bgp_format_next_hop(eattr *a, byte *buf, int buflen UNUSED)
 +bgp_decode_med(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
  {
 -  ip_addr *ipp = (ip_addr *) a->u.ptr->data;
 -#ifdef IPV6
 -  /* in IPv6, we might have two addresses in NEXT HOP */
 -  if ((a->u.ptr->length == NEXT_HOP_LENGTH) && ipa_nonzero(ipp[1]))
 -    {
 -      bsprintf(buf, "%I %I", ipp[0], ipp[1]);
 -      return;
 -    }
 -#endif
 +  if (len != 4)
 +    WITHDRAW(BAD_LENGTH, "MULTI_EXIT_DISC", len);
  
 -  bsprintf(buf, "%I", ipp[0]);
 +  u32 val = get_u32(data);
 +  bgp_set_attr_u32(to, s->pool, BA_MULTI_EXIT_DISC, flags, val);
  }
  
 -static int
 -bgp_check_aggregator(struct bgp_proto *p, byte *a UNUSED, int len)
 +
 +static void
 +bgp_export_local_pref(struct bgp_export_state *s, eattr *a)
  {
-   if (!s->proto->is_interior)
 -  int exp_len = p->as4_session ? 8 : 6;
 -  
 -  return (len == exp_len) ? 0 : WITHDRAW;
++  if (!s->proto->is_interior && !s->proto->cf->allow_local_pref)
 +    UNSET(a);
  }
  
  static void
 -bgp_format_aggregator(eattr *a, byte *buf, int buflen UNUSED)
 +bgp_decode_local_pref(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data, uint len, ea_list **to)
  {
-   if (!s->proto->is_interior)
 -  struct adata *ad =  a->u.ptr;
 -  byte *data = ad->data;
 -  u32 as;
++  if (!s->proto->is_interior && !s->proto->cf->allow_local_pref)
 +    DISCARD(BAD_EBGP, "LOCAL_PREF");
  
 -  as = get_u32(data);
 -  data += 4;
 +  if (len != 4)
 +    WITHDRAW(BAD_LENGTH, "LOCAL_PREF", len);
  
 -  bsprintf(buf, "%d.%d.%d.%d AS%u", data[0], data[1], data[2], data[3], as);
 +  u32 val = get_u32(data);
 +  bgp_set_attr_u32(to, s->pool, BA_LOCAL_PREF, flags, val);
  }
  
 -static int
 -bgp_check_community(struct bgp_proto *p UNUSED, byte *a UNUSED, int len)
 +
 +static void
 +bgp_decode_atomic_aggr(struct bgp_parse_state *s, uint code UNUSED, uint flags, byte *data UNUSED, uint len, ea_list **to)
  {
 -  return ((len % 4) == 0) ? 0 : WITHDRAW;
 +  if (len != 0)
 +    DISCARD(BAD_LENGTH, "ATOMIC_AGGR", len);
 +
 +  bgp_set_attr_data(to, s->pool, BA_ATOMIC_AGGR, flags, NULL, 0);
  }
  
  static int
diff --cc proto/bgp/bgp.c
index 86f7be1bc87c09c588c519abe318e2234bec7a58,f706e76e64b78794d92118a92200d62bce828daf..b9a1d157f8912ffad69b3c4e478838cc278b8845
@@@ -974,15 -783,16 +974,15 @@@ err
  static struct bgp_proto *
  bgp_find_proto(sock *sk)
  {
 -  struct proto_config *pc;
 +  struct bgp_proto *p;
  
 -  WALK_LIST(pc, config->protos)
 -    if ((pc->protocol == &proto_bgp) && pc->proto)
 -      {
 -      struct bgp_proto *p = (struct bgp_proto *) pc->proto;
 -      if (ipa_equal(p->cf->remote_ip, sk->daddr) &&
 -          (!p->cf->iface || (p->cf->iface == sk->iface)))
 -        return p;
 -      }
 +  WALK_LIST(p, proto_list)
 +    if ((p->p.proto == &proto_bgp) &&
 +      ipa_equal(p->cf->remote_ip, sk->daddr) &&
-       (!ipa_is_link_local(sk->daddr) || (p->cf->iface == sk->iface)) &&
++      (!p->cf->iface  || (p->cf->iface == sk->iface)) &&
 +      (ipa_zero(p->cf->local_ip) || ipa_equal(p->cf->local_ip, sk->saddr)) &&
 +      (p->cf->local_port == sk->sport))
 +      return p;
  
    return NULL;
  }
@@@ -1595,95 -1299,67 +1595,95 @@@ bgp_postconfig(struct proto_config *CF
  
  
    /* EBGP direct by default, IBGP multihop by default */
 -  if (c->multihop < 0)
 -    c->multihop = internal ? 64 : 0;
 -
 -  /* Different default for gw_mode */
 -  if (!c->gw_mode)
 -    c->gw_mode = c->multihop ? GW_RECURSIVE : GW_DIRECT;
 -
 -  /* Different default based on rs_client */
 -  if (!c->missing_lladdr)
 -    c->missing_lladdr = c->rs_client ? MLL_IGNORE : MLL_SELF;
 -
 -  /* Disable after error incompatible with restart limit action */
 -  if (c->c.in_limit && (c->c.in_limit->action == PLA_RESTART) && c->disable_after_error)
 -    c->c.in_limit->action = PLA_DISABLE;
 +  if (cf->multihop < 0)
 +    cf->multihop = internal ? 64 : 0;
  
  
 -  if (!c->local_as)
 +  if (!cf->local_as)
      cf_error("Local AS number must be set");
  
 -  if (ipa_zero(c->remote_ip))
 +  if (ipa_zero(cf->remote_ip))
      cf_error("Neighbor must be configured");
  
 -  if (!c->remote_as)
 +  if (!cf->remote_as)
      cf_error("Remote AS number must be set");
  
-   // if (ipa_is_link_local(c->remote_ip) && !c->iface)
-   //   cf_error("Link-local neighbor address requires specified interface");
-   if (!ipa_is_link_local(cf->remote_ip) != !cf->iface)
-     cf_error("Link-local address and interface scope must be used together");
 -  if (ipa_is_link_local(c->remote_ip) && !c->iface)
++  if (ipa_is_link_local(cf->remote_ip) && !cf->iface)
+     cf_error("Link-local neighbor address requires specified interface");
  
 -  if (!(c->capabilities && c->enable_as4) && (c->remote_as > 0xFFFF))
 +  if (!(cf->capabilities && cf->enable_as4) && (cf->remote_as > 0xFFFF))
      cf_error("Neighbor AS number out of range (AS4 not available)");
  
 -  if (!internal && c->rr_client)
 +  if (!internal && cf->rr_client)
      cf_error("Only internal neighbor can be RR client");
  
 -  if (internal && c->rs_client)
 +  if (internal && cf->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 (!cf->confederation && cf->confederation_member)
 +    cf_error("Confederation ID must be set for member sessions");
  
 -  if (c->multihop && (ipa_is_link_local(c->remote_ip) ||
 -                    ipa_is_link_local(c->source_addr)))
 +  if (cf->multihop && (ipa_is_link_local(cf->local_ip) ||
 +                     ipa_is_link_local(cf->remote_ip)))
      cf_error("Multihop BGP cannot be used with link-local addresses");
  
 -  if (c->multihop && c->iface)
++  if (cf->multihop && cf->iface)
+     cf_error("Multihop BGP cannot be bound to interface");
 -  if (c->multihop && c->check_link)
 +  if (cf->multihop && cf->check_link)
      cf_error("Multihop BGP cannot depend on link state");
  
 -  if (c->multihop && c->bfd && ipa_zero(c->source_addr))
 -    cf_error("Multihop BGP with BFD requires specified source address");
 +  if (cf->multihop && cf->bfd && ipa_zero(cf->local_ip))
 +    cf_error("Multihop BGP with BFD requires specified local address");
  
 -  if ((c->gw_mode == GW_RECURSIVE) && c->c.table->sorted)
 -    cf_error("BGP in recursive mode prohibits sorted table");
  
 -  if (c->deterministic_med && c->c.table->sorted)
 -    cf_error("BGP with deterministic MED prohibits sorted table");
 +  struct bgp_channel_config *cc;
 +  WALK_LIST(cc, CF->channels)
 +  {
 +    /* Disable after error incompatible with restart limit action */
 +    if ((cc->c.in_limit.action == PLA_RESTART) && cf->disable_after_error)
 +      cc->c.in_limit.action = PLA_DISABLE;
 +
 +    /* Different default based on rs_client */
 +    if (!cc->missing_lladdr)
 +      cc->missing_lladdr = cf->rs_client ? MLL_IGNORE : MLL_SELF;
 +
 +    /* Different default for gw_mode */
 +    if (!cc->gw_mode)
 +      cc->gw_mode = cf->multihop ? GW_RECURSIVE : GW_DIRECT;
 +
 +    /* Default based on proto config */
 +    if (cc->gr_able == 0xff)
 +      cc->gr_able = (cf->gr_mode == BGP_GR_ABLE);
 +
 +    /* Default values of IGP tables */
 +    if ((cc->gw_mode == GW_RECURSIVE) && !cc->desc->no_igp)
 +    {
 +      if (!cc->igp_table_ip4 && (bgp_cc_is_ipv4(cc) || cc->ext_next_hop))
 +      cc->igp_table_ip4 = bgp_default_igp_table(cf, cc, NET_IP4);
 +
 +      if (!cc->igp_table_ip6 && (bgp_cc_is_ipv6(cc) || cc->ext_next_hop))
 +      cc->igp_table_ip6 = bgp_default_igp_table(cf, cc, NET_IP6);
  
 -  if (c->secondary && !c->c.table->sorted)
 -    cf_error("BGP with secondary option requires sorted table");
 +      if (cc->igp_table_ip4 && bgp_cc_is_ipv6(cc) && !cc->ext_next_hop)
 +      cf_error("Mismatched IGP table type");
 +
 +      if (cc->igp_table_ip6 && bgp_cc_is_ipv4(cc) && !cc->ext_next_hop)
 +      cf_error("Mismatched IGP table type");
 +    }
 +
 +    if (cf->multihop && (cc->gw_mode == GW_DIRECT))
 +      cf_error("Multihop BGP cannot use direct gateway mode");
 +
 +    if ((cc->gw_mode == GW_RECURSIVE) && cc->c.table->sorted)
 +      cf_error("BGP in recursive mode prohibits sorted table");
 +
 +    if (cf->deterministic_med && cc->c.table->sorted)
 +      cf_error("BGP with deterministic MED prohibits sorted table");
 +
 +    if (cc->secondary && !cc->c.table->sorted)
 +      cf_error("BGP with secondary option requires sorted table");
 +  }
  }
  
  static int
diff --cc proto/bgp/bgp.h
index 61d3600d9695747a1db0cf041b39ec8cd3aaf20e,e47a0eb1d8c894bc35afd878a58c5724aa68877c..7ffcb68ad1dfda41c71d2c43ee72e2571974c940
@@@ -100,11 -44,13 +100,12 @@@ struct bgp_config 
    u32 rr_cluster_id;                  /* Route reflector cluster ID, if different from local ID */
    int rr_client;                      /* Whether neighbor is RR client of me */
    int rs_client;                      /* Whether neighbor is RS client of me */
 -  int advertise_ipv4;                 /* Whether we should add IPv4 capability advertisement to OPEN message */
 +  u32 confederation;                  /* Confederation ID, or zero if confeds not active */
 +  int confederation_member;           /* Whether neighbor AS is member of our confederation */
    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) */
 -  int add_path;                               /* Use ADD-PATH extension [RFC7911] */
    int allow_local_as;                 /* Allow that number of local ASNs in incoming AS_PATHs */
+   int allow_local_pref;                       /* Allow LOCAL_PREF in EBGP sessions */
    int gr_mode;                                /* Graceful restart mode (BGP_GR_*) */
    int setkey;                         /* Set MD5 password to system SA/SP database */
    unsigned gr_time;                   /* Graceful restart timeout */
index 63e82285f842d6e96751762d7806a2d1a1522eef,55c602f1e80121177de323b8fee27e2f128a75cf..941ae5b6a12df7efc08fa992f958ee6e74747c13
@@@ -120,12 -111,24 +120,13 @@@ bgp_proto
   | bgp_proto ENABLE AS4 bool ';' { BGP_CFG->enable_as4 = $4; }
   | bgp_proto ENABLE EXTENDED MESSAGES bool ';' { BGP_CFG->enable_extended_messages = $5; }
   | bgp_proto CAPABILITIES bool ';' { BGP_CFG->capabilities = $3; }
 - | bgp_proto ADVERTISE IPV4 bool ';' { BGP_CFG->advertise_ipv4 = $4; }
   | bgp_proto PASSWORD text ';' { BGP_CFG->password = $3; }
   | bgp_proto SETKEY bool ';' { BGP_CFG->setkey = $3; }
 - | bgp_proto ROUTE LIMIT expr ';' {
 -     this_proto->in_limit = cfg_allocz(sizeof(struct proto_limit));
 -     this_proto->in_limit->limit = $4;
 -     this_proto->in_limit->action = PLA_RESTART;
 -     log(L_WARN "%s: Route limit option is deprecated, use import limit", this_proto->name);
 -   }
   | 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 ADD PATHS RX ';' { BGP_CFG->add_path = ADD_PATH_RX; }
 - | bgp_proto ADD PATHS TX ';' { BGP_CFG->add_path = ADD_PATH_TX; }
 - | bgp_proto ADD PATHS bool ';' { BGP_CFG->add_path = $4 ? ADD_PATH_FULL : 0; }
 - | bgp_proto ALLOW BGP_LOCAL_PREF bool ';' { BGP_CFG->allow_local_pref = $4; }
   | bgp_proto ALLOW LOCAL AS ';' { BGP_CFG->allow_local_as = -1; }
   | bgp_proto ALLOW LOCAL AS expr ';' { BGP_CFG->allow_local_as = $5; }
++ | bgp_proto ALLOW BGP_LOCAL_PREF bool ';' { BGP_CFG->allow_local_pref = $4; }
   | bgp_proto GRACEFUL RESTART bool ';' { BGP_CFG->gr_mode = $4; }
   | bgp_proto GRACEFUL RESTART AWARE ';' { BGP_CFG->gr_mode = BGP_GR_AWARE; }
   | bgp_proto GRACEFUL RESTART TIME expr ';' { BGP_CFG->gr_time = $5; }