]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Merge commit '1e8721e2aeccfbc3f533e8b8abc07582cee77e9a' into int-new
authorOndrej Zajicek (work) <santiago@crfreenet.org>
Thu, 7 Dec 2017 20:54:47 +0000 (21:54 +0100)
committerOndrej Zajicek (work) <santiago@crfreenet.org>
Thu, 7 Dec 2017 20:54:47 +0000 (21:54 +0100)
15 files changed:
1  2 
doc/bird.sgml
filter/filter.c
filter/filter.h
nest/config.Y
nest/proto.c
nest/protocol.h
nest/route.h
proto/babel/packets.c
proto/bgp/bgp.c
proto/bgp/bgp.h
proto/bgp/packets.c
proto/radv/config.Y
proto/radv/packets.c
proto/radv/radv.c
proto/radv/radv.h

diff --cc doc/bird.sgml
Simple merge
diff --cc filter/filter.c
Simple merge
diff --cc filter/filter.h
index 6c81b9bcda003bcbbf609c2095e8f870542ae82b,72b37461788ae508d702563603bca6e7bea340b6..c277b67cb9c5ea5d5af985c02f8b15f0ab79fa43
@@@ -146,8 -168,7 +146,9 @@@ void val_format(struct f_val v, buffer 
  #define T_ENUM_RTC 0x33
  #define T_ENUM_RTD 0x34
  #define T_ENUM_ROA 0x35
 -#define T_ENUM_RA_PREFERENCE 0x36
 +#define T_ENUM_NETTYPE 0x36
++#define T_ENUM_RA_PREFERENCE 0x37
 +
  /* new enums go here */
  #define T_ENUM_EMPTY 0x3f     /* Special hack for atomic_aggr */
  
diff --cc nest/config.Y
index ad45a39dcb7ecf5794965397600c5c7c71cc79a9,f51be6ea0d70a703530e4826494a7edb8ae30d06..555c9e055ad17f37499fc5e15f29f07b466033cf
@@@ -756,12 -696,12 +756,12 @@@ echo_size
     }
   ;
  
- CF_CLI(DISABLE, proto_patt, <protocol> | \"<pattern>\" | all, [[Disable protocol]])
- { proto_apply_cmd($2, proto_cmd_disable, 1, 0); } ;
- CF_CLI(ENABLE, proto_patt, <protocol> | \"<pattern>\" | all, [[Enable protocol]])
- { proto_apply_cmd($2, proto_cmd_enable, 1, 0); } ;
- CF_CLI(RESTART, proto_patt, <protocol> | \"<pattern>\" | all, [[Restart protocol]])
- { proto_apply_cmd($2, proto_cmd_restart, 1, 0); } ;
 -CF_CLI(DISABLE, proto_patt text_or_none, (<protocol> | \"<pattern>\" | all) [message], [[Disable protocol]])
++CF_CLI(DISABLE, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Disable protocol]])
+ { proto_apply_cmd($2, proto_cmd_disable, 1, (uintptr_t) $3); } ;
 -CF_CLI(ENABLE, proto_patt text_or_none, (<protocol> | \"<pattern>\" | all) [message], [[Enable protocol]])
++CF_CLI(ENABLE, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Enable protocol]])
+ { proto_apply_cmd($2, proto_cmd_enable, 1, (uintptr_t) $3); } ;
 -CF_CLI(RESTART, proto_patt text_or_none, (<protocol> | \"<pattern>\" | all) [message], [[Restart protocol]])
++CF_CLI(RESTART, proto_patt opttext, (<protocol> | \"<pattern>\" | all) [message], [[Restart protocol]])
+ { proto_apply_cmd($2, proto_cmd_restart, 1, (uintptr_t) $3); } ;
  CF_CLI(RELOAD, proto_patt, <protocol> | \"<pattern>\" | all, [[Reload protocol]])
  { proto_apply_cmd($2, proto_cmd_reload, 1, CMD_RELOAD); } ;
  CF_CLI(RELOAD IN, proto_patt, <protocol> | \"<pattern>\" | all, [[Reload protocol (just imported routes)]])
diff --cc nest/proto.c
index ecc3b0fe3b31380338132ccd519bdcb057cbcfdf,552d53ae110b0e6e2edc1239f3159d0c6c0e1351..a2a2bc7e55f33c13bd7bb25bfc1332c8154f7b37
@@@ -970,21 -602,20 +970,22 @@@ proto_rethink_goal(struct proto *p
    struct protocol *q;
    byte goal;
  
 -  if (p->reconfiguring && p->core_state == FS_HUNGRY && p->proto_state == PS_DOWN)
 -    {
 -      struct proto_config *nc = p->cf_new;
 -      DBG("%s has shut down for reconfiguration\n", p->name);
 -      p->cf->proto = NULL;
 -      config_del_obstacle(p->cf->global);
 -      rem_node(&p->n);
 -      rem_node(&p->glob_node);
 -      mb_free(p->message);
 -      mb_free(p);
 -      if (!nc)
 -      return;
 -      p = proto_init(nc);
 -    }
 +  if (p->reconfiguring && !p->active)
 +  {
 +    struct proto_config *nc = p->cf_new;
 +    node *n = p->n.prev;
 +    DBG("%s has shut down for reconfiguration\n", p->name);
 +    p->cf->proto = NULL;
 +    config_del_obstacle(p->cf->global);
 +    proto_remove_channels(p);
 +    rem_node(&p->n);
 +    rfree(p->event);
++    mb_free(p->message);
 +    mb_free(p);
 +    if (!nc)
 +      return;
 +    p = proto_init(nc, n);
 +  }
  
    /* Determine what state we want to reach */
    if (p->disabled || p->reconfiguring)
@@@ -1331,12 -1094,87 +1332,45 @@@ proto_schedule_down(struct proto *p, by
  
    p->down_sched = restart ? PDS_RESTART : PDS_DISABLE;
    p->down_code = code;
 -  tm_start_max(proto_shutdown_timer, restart ? 2 : 0);
 +  tm_start_max(proto_shutdown_timer, restart ? 250 MS : 0);
  }
  
+ /**
+  * proto_set_message - set administrative message to protocol
+  * @p: protocol
+  * @msg: message
+  * @len: message length (-1 for NULL-terminated string)
+  *
+  * The function sets administrative message (string) related to protocol state
+  * change. It is called by the nest code for manual enable/disable/restart
+  * commands all routes to the protocol, and by protocol-specific code when the
+  * protocol state change is initiated by the protocol. Using NULL message clears
+  * the last message. The message string may be either NULL-terminated or with an
+  * explicit length.
+  */
+ void
+ proto_set_message(struct proto *p, char *msg, int len)
+ {
+   mb_free(p->message);
+   p->message = NULL;
+   if (!msg || !len)
+     return;
+   if (len < 0)
+     len = strlen(msg);
+   if (!len)
+     return;
+   p->message = mb_alloc(proto_pool, len + 1);
+   memcpy(p->message, msg, len);
+   p->message[len] = 0;
+ }
  
 -/**
 - * proto_request_feeding - request feeding routes to the protocol
 - * @p: given protocol 
 - *
 - * Sometimes it is needed to send again all routes to the
 - * protocol. This is called feeding and can be requested by this
 - * function. This would cause protocol export state transition
 - * to ES_FEEDING (during feeding) and when completed, it will
 - * switch back to ES_READY. This function can be called even
 - * when feeding is already running, in that case it is restarted.
 - */
 -void
 -proto_request_feeding(struct proto *p)
 -{
 -  ASSERT(p->proto_state == PS_UP);
 -
 -  /* Do nothing if we are still waiting for feeding */
 -  if (p->export_state == ES_DOWN)
 -    return;
 -
 -  /* If we are already feeding, we want to restart it */
 -  if (p->export_state == ES_FEEDING)
 -    {
 -      /* Unless feeding is in initial state */
 -      if (p->attn->hook == proto_feed_initial)
 -      return;
 -
 -      rt_feed_baby_abort(p);
 -    }
 -
 -  /* FIXME: This should be changed for better support of multitable protos */
 -  struct announce_hook *ah;
 -  for (ah = p->ahooks; ah; ah = ah->next)
 -    proto_reset_limit(ah->out_limit);
 -
 -  /* Hack: reset exp_routes during refeed, and do not decrease it later */
 -  p->stats.exp_routes = 0;
 -
 -  proto_schedule_feed(p, 0);
 -  proto_log_state_change(p);
 -}
 -
  static const char *
 -proto_limit_name(struct proto_limit *l)
 +channel_limit_name(struct channel_limit *l)
  {
    const char *actions[] = {
      [PLA_WARN] = "warn",
@@@ -1641,37 -1550,34 +1675,39 @@@ proto_cmd_show(struct proto *p, uintptr
          proto_state_name(p),
          tbuf,
          buf);
 +
    if (verbose)
 +  {
 +    if (p->cf->dsc)
 +      cli_msg(-1006, "  Description:    %s", p->cf->dsc);
++    if (p->message)
++      cli_msg(-1006, "  Message:        %s", p->message);
 +    if (p->cf->router_id)
 +      cli_msg(-1006, "  Router ID:      %R", p->cf->router_id);
 +    if (p->vrf)
 +      cli_msg(-1006, "  VRF:            %s", p->vrf->name);
 +
 +    if (p->proto->show_proto_info)
 +      p->proto->show_proto_info(p);
 +    else
      {
 -      if (p->cf->dsc)
 -      cli_msg(-1006, "  Description:    %s", p->cf->dsc);
 -
 -      if (p->message)
 -      cli_msg(-1006, "  Message:        %s", p->message);
 -
 -      if (p->cf->router_id)
 -      cli_msg(-1006, "  Router ID:      %R", p->cf->router_id);
 -
 -      if (p->proto->show_proto_info)
 -      p->proto->show_proto_info(p);
 -      else
 -      proto_show_basic_info(p);
 -
 -      cli_msg(-1006, "");
 +      struct channel *c;
 +      WALK_LIST(c, p->channels)
 +      channel_show_info(c);
      }
 +
 +    cli_msg(-1006, "");
 +  }
  }
  
  void
- proto_cmd_disable(struct proto *p, uint arg UNUSED, int cnt UNUSED)
+ proto_cmd_disable(struct proto *p, uintptr_t arg, int cnt UNUSED)
  {
    if (p->disabled)
 -    {
 -      cli_msg(-8, "%s: already disabled", p->name);
 -      return;
 -    }
 +  {
 +    cli_msg(-8, "%s: already disabled", p->name);
 +    return;
 +  }
  
    log(L_INFO "Disabling protocol %s", p->name);
    p->disabled = 1;
  }
  
  void
- proto_cmd_enable(struct proto *p, uint arg UNUSED, int cnt UNUSED)
+ proto_cmd_enable(struct proto *p, uintptr_t arg, int cnt UNUSED)
  {
    if (!p->disabled)
 -    {
 -      cli_msg(-10, "%s: already enabled", p->name);
 -      return;
 -    }
 +  {
 +    cli_msg(-10, "%s: already enabled", p->name);
 +    return;
 +  }
  
    log(L_INFO "Enabling protocol %s", p->name);
    p->disabled = 0;
  }
  
  void
- proto_cmd_restart(struct proto *p, uint arg UNUSED, int cnt UNUSED)
+ proto_cmd_restart(struct proto *p, uintptr_t arg, int cnt UNUSED)
  {
    if (p->disabled)
 -    {
 -      cli_msg(-8, "%s: already disabled", p->name);
 -      return;
 -    }
 +  {
 +    cli_msg(-8, "%s: already disabled", p->name);
 +    return;
 +  }
  
    log(L_INFO "Restarting protocol %s", p->name);
    p->disabled = 1;
  }
  
  void
- proto_cmd_reload(struct proto *p, uint dir, int cnt UNUSED)
+ proto_cmd_reload(struct proto *p, uintptr_t dir, int cnt UNUSED)
  {
 +  struct channel *c;
 +
    if (p->disabled)
 -    {
 -      cli_msg(-8, "%s: already disabled", p->name);
 -      return;
 -    }
 +  {
 +    cli_msg(-8, "%s: already disabled", p->name);
 +    return;
 +  }
  
    /* If the protocol in not UP, it has no routes */
    if (p->proto_state != PS_UP)
@@@ -1765,27 -1677,31 +1804,27 @@@ proto_cmd_mrtdump(struct proto *p, uint
  }
  
  static void
- proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uint, int), uint arg)
+ proto_apply_cmd_symbol(struct symbol *s, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg)
  {
    if (s->class != SYM_PROTO)
 -    {
 -      cli_msg(9002, "%s is not a protocol", s->name);
 -      return;
 -    }
 +  {
 +    cli_msg(9002, "%s is not a protocol", s->name);
 +    return;
 +  }
  
    cmd(((struct proto_config *)s->def)->proto, arg, 0);
    cli_msg(0, "");
  }
  
  static void
- proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uint, int), uint arg)
+ proto_apply_cmd_patt(char *patt, void (* cmd)(struct proto *, uintptr_t, int), uintptr_t arg)
  {
 +  struct proto *p;
    int cnt = 0;
  
 -  node *nn;
 -  WALK_LIST(nn, proto_list)
 -    {
 -      struct proto *p = SKIP_BACK(struct proto, glob_node, nn);
 -
 -      if (!patt || patmatch(patt, p->name))
 -      cmd(p, arg, cnt++);
 -    }
 +  WALK_LIST(p, proto_list)
 +    if (!patt || patmatch(patt, p->name))
 +      cmd(p, arg, cnt++);
  
    if (!cnt)
      cli_msg(8003, "No protocols match");
diff --cc nest/protocol.h
index d7e84a4417d3091c014f18b7a428e03f4892f69b,5aca9a4eafcfae0f8d9adc0c1232248e102d499b..c8f3736782a055baf57d489476c940d551f6c411
@@@ -148,20 -146,26 +148,21 @@@ struct proto 
    char *name;                         /* Name of this instance (== cf->name) */
    u32 debug;                          /* Debugging flags */
    u32 mrtdump;                                /* MRTDump flags */
 -  unsigned preference;                        /* Default route preference */
 -  byte accept_ra_types;                       /* Which types of route announcements are accepted (RA_OPTIMAL or RA_ANY) */
 +  uint active_channels;                       /* Number of active channels */
 +  byte net_type;                      /* Protocol network type (NET_*), 0 for undefined */
    byte disabled;                      /* Manually disabled */
    byte proto_state;                   /* Protocol state machine (PS_*, see below) */
 -  byte core_state;                    /* Core state machine (FS_*, see below) */
 -  byte export_state;                  /* Route export state (ES_*, see below) */
 +  byte active;                                /* From PS_START to cleanup after PS_STOP */
 +  byte do_start;                      /* Start actions are scheduled */
 +  byte do_stop;                               /* Stop actions are scheduled */
    byte reconfiguring;                 /* We're shutting down due to reconfiguration */
 -  byte refeeding;                     /* We are refeeding (valid only if export_state == ES_FEEDING) */
 -  byte flushing;                      /* Protocol is flushed in current flush loop round */
    byte gr_recovery;                   /* Protocol should participate in graceful restart recovery */
 -  byte gr_lock;                               /* Graceful restart mechanism should wait for this proto */
 -  byte gr_wait;                               /* Route export to protocol is postponed until graceful restart */
    byte down_sched;                    /* Shutdown is scheduled for later (PDS_*) */
    byte down_code;                     /* Reason for shutdown (PDC_* codes) */
 -  byte merge_limit;                   /* Maximal number of nexthops for RA_MERGED */
    u32 hash_key;                               /* Random key used for hashing of neighbors */
 -  bird_clock_t last_state_change;     /* Time of last state transition */
 +  btime last_state_change;            /* Time of last state transition */
    char *last_state_name_announced;    /* Last state name we've announced to the user */
 -  struct proto_stats stats;           /* Current protocol statistics */
+   char *message;                      /* State-change message, allocated from proto_pool */
  
    /*
     *  General protocol hooks:
@@@ -235,9 -248,15 +236,10 @@@ struct proto_spec 
  #define PDC_OUT_LIMIT_HIT     0x23    /* Route export limit reached */
  
  
 -void *proto_new(struct proto_config *, unsigned size);
 +void *proto_new(struct proto_config *);
  void *proto_config_new(struct protocol *, int class);
  void proto_copy_config(struct proto_config *dest, struct proto_config *src);
 -void proto_request_feeding(struct proto *p);
 -
 -static inline void
 -proto_copy_rest(struct proto_config *dest, struct proto_config *src, unsigned size)
 -{ memcpy(dest + 1, src + 1, size - sizeof(struct proto_config)); }
+ void proto_set_message(struct proto *p, char *msg, int len);
  
  void graceful_restart_recovery(void);
  void graceful_restart_init(void);
@@@ -247,18 -266,18 +249,18 @@@ void channel_graceful_restart_unlock(st
  
  #define DEFAULT_GR_WAIT       240
  
 -void proto_show_limit(struct proto_limit *l, const char *dsc);
 -void proto_show_basic_info(struct proto *p);
 +void channel_show_limit(struct channel_limit *l, const char *dsc);
 +void channel_show_info(struct channel *c);
  
- void proto_cmd_show(struct proto *, uint, int);
- void proto_cmd_disable(struct proto *, uint, int);
- void proto_cmd_enable(struct proto *, uint, int);
- void proto_cmd_restart(struct proto *, uint, int);
- void proto_cmd_reload(struct proto *, uint, int);
- void proto_cmd_debug(struct proto *, uint, int);
- void proto_cmd_mrtdump(struct proto *, uint, int);
+ void proto_cmd_show(struct proto *, uintptr_t, int);
+ void proto_cmd_disable(struct proto *, uintptr_t, int);
+ void proto_cmd_enable(struct proto *, uintptr_t, int);
+ void proto_cmd_restart(struct proto *, uintptr_t, int);
+ void proto_cmd_reload(struct proto *, uintptr_t, int);
+ void proto_cmd_debug(struct proto *, uintptr_t, int);
+ void proto_cmd_mrtdump(struct proto *, uintptr_t, int);
  
- void proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uint, int), int restricted, uint arg);
+ void proto_apply_cmd(struct proto_spec ps, void (* cmd)(struct proto *, uintptr_t, int), int restricted, uintptr_t arg);
  struct proto *proto_get_named(struct symbol *, struct protocol *);
  
  #define CMD_RELOAD    0
diff --cc nest/route.h
Simple merge
index 5b356fae78730d6977cc4a1fd4eaaccdff2f14b4,768858d03fa9c4db864b43ea1bca2b65c036e88c..dd86222a4b4d837ea744729c5e93b0179a61fbe9
@@@ -588,36 -491,11 +597,36 @@@ babel_read_update(struct babel_tlv *hdr
      break;
  
    case BABEL_AE_IP4:
 -    /* TODO */
 -    return PARSE_IGNORE;
 +    if (tlv->plen > IP4_MAX_PREFIX_LENGTH)
 +      return PARSE_ERROR;
 +
 +    /* Cannot omit data if there is no saved prefix */
 +    if (tlv->omitted && !state->def_ip4_prefix_seen)
 +      return PARSE_ERROR;
 +
 +    /* Update must have next hop, unless it is retraction */
 +    if (ipa_zero(state->next_hop_ip4) && (msg->metric != BABEL_INFINITY))
 +      return PARSE_ERROR;
 +
 +    /* Merge saved prefix and received prefix parts */
 +    memcpy(buf, state->def_ip4_prefix, tlv->omitted);
 +    memcpy(buf + tlv->omitted, tlv->addr, len);
 +
 +    ip4_addr prefix4 = get_ip4(buf);
 +    net_fill_ip4(&msg->net, prefix4, tlv->plen);
 +
-     if (tlv->flags & BABEL_FLAG_DEF_PREFIX)
++    if (tlv->flags & BABEL_UF_DEF_PREFIX)
 +    {
 +      put_ip4(state->def_ip4_prefix, prefix4);
 +      state->def_ip4_prefix_seen = 1;
 +    }
 +
 +    msg->next_hop = state->next_hop_ip4;
 +
 +    break;
  
    case BABEL_AE_IP6:
 -    if (tlv->plen > MAX_PREFIX_LENGTH)
 +    if (tlv->plen > IP6_MAX_PREFIX_LENGTH)
        return PARSE_ERROR;
  
      /* Cannot omit data if there is no saved prefix */
      memcpy(buf, state->def_ip6_prefix, tlv->omitted);
      memcpy(buf + tlv->omitted, tlv->addr, len);
  
 -    msg->plen = tlv->plen;
 -    msg->prefix = ipa_from_ip6(get_ip6(buf));
 +    ip6_addr prefix6 = get_ip6(buf);
 +    net_fill_ip6(&msg->net, prefix6, tlv->plen);
  
-     if (tlv->flags & BABEL_FLAG_DEF_PREFIX)
+     if (tlv->flags & BABEL_UF_DEF_PREFIX)
      {
 -      put_ip6(state->def_ip6_prefix, msg->prefix);
 +      put_ip6(state->def_ip6_prefix, prefix6);
        state->def_ip6_prefix_seen = 1;
      }
  
-     if (tlv->flags & BABEL_FLAG_ROUTER_ID)
+     if (tlv->flags & BABEL_UF_ROUTER_ID)
      {
 -      state->router_id = ((u64) _I2(msg->prefix)) << 32 | _I3(msg->prefix);
 +      state->router_id = ((u64) _I2(prefix6)) << 32 | _I3(prefix6);
        state->router_id_seen = 1;
      }
 +
 +    msg->next_hop = state->next_hop_ip6;
 +
      break;
  
    case BABEL_AE_IP6_LL:
@@@ -731,30 -582,8 +740,30 @@@ babel_write_update(struct babel_tlv *hd
    else
    {
      tlv->ae = BABEL_AE_IP6;
 -    tlv->plen = msg->plen;
 -    put_ip6_px(tlv->addr, msg->prefix, msg->plen);
 +    tlv->plen = net6_pxlen(&msg->net);
 +
 +    /* Address compression - omit initial matching bytes */
 +    u8 buf[16], omit;
 +    put_ip6(buf, net6_prefix(&msg->net));
 +    omit = bytes_equal(buf, state->def_ip6_prefix,
 +                     MIN(tlv->plen, state->def_ip6_pxlen) / 8);
 +
 +    if (omit > 0)
 +    {
 +      memcpy(tlv->addr, buf + omit, NET_SIZE(&msg->net) - omit);
 +
 +      tlv->omitted = omit;
 +      tlv->length -= omit;
 +      len -= omit;
 +    }
 +    else
 +    {
 +      put_ip6_px(tlv->addr, &msg->net);
-       tlv->flags |= BABEL_FLAG_DEF_PREFIX;
++      tlv->flags |= BABEL_UF_DEF_PREFIX;
 +
 +      put_ip6(state->def_ip6_prefix, net6_prefix(&msg->net));
 +      state->def_ip6_pxlen = tlv->plen;
 +    }
    }
  
    put_time16(&tlv->interval, msg->interval);
diff --cc proto/bgp/bgp.c
index b0814791d7972ce3434ea0659c28a2579106f87c,b99672f55ca8f6d8c653b0641847d640d22de563..30fe75ba77f425d3764d06542e29e11cd3a978ee
@@@ -403,28 -290,25 +403,28 @@@ bgp_update_startup_delay(struct bgp_pro
  }
  
  static void
- bgp_graceful_close_conn(struct bgp_conn *conn, uint subcode)
+ bgp_graceful_close_conn(struct bgp_conn *conn, uint subcode, byte *data, uint len)
  {
    switch (conn->state)
 -    {
 -    case BS_IDLE:
 -    case BS_CLOSE:
 -      return;
 -    case BS_CONNECT:
 -    case BS_ACTIVE:
 -      bgp_conn_enter_idle_state(conn);
 -      return;
 -    case BS_OPENSENT:
 -    case BS_OPENCONFIRM:
 -    case BS_ESTABLISHED:
 -      bgp_error(conn, 6, subcode, data, len);
 -      return;
 -    default:
 -      bug("bgp_graceful_close_conn: Unknown state %d", conn->state);
 -    }
 +  {
 +  case BS_IDLE:
 +  case BS_CLOSE:
 +    return;
 +
 +  case BS_CONNECT:
 +  case BS_ACTIVE:
 +    bgp_conn_enter_idle_state(conn);
 +    return;
 +
 +  case BS_OPENSENT:
 +  case BS_OPENCONFIRM:
 +  case BS_ESTABLISHED:
-     bgp_error(conn, 6, subcode, NULL, 0);
++    bgp_error(conn, 6, subcode, data, len);
 +    return;
 +
 +  default:
 +    bug("bgp_graceful_close_conn: Unknown state %d", conn->state);
 +  }
  }
  
  static void
@@@ -601,12 -416,11 +601,12 @@@ bgp_conn_leave_established_state(struc
    BGP_TRACE(D_EVENTS, "BGP session closed");
    p->conn = NULL;
  
 -  bgp_free_prefix_table(p);
 -  bgp_free_bucket_table(p);
 +  // XXXX free these tables to avoid memory leak during graceful restart
 +  // bgp_free_prefix_table(p);
 +  // bgp_free_bucket_table(p);
  
    if (p->p.proto_state == PS_UP)
-     bgp_stop(p, 0);
+     bgp_stop(p, 0, NULL, 0);
  }
  
  void
@@@ -1119,34 -967,34 +1119,34 @@@ bgp_neigh_notify(neighbor *n
    int prepare = (ps == PS_START) && (p->start_state == BSS_PREPARE);
  
    if (n->scope <= 0)
 +  {
 +    if (!prepare)
      {
 -      if (!prepare)
 -        {
 -        BGP_TRACE(D_EVENTS, "Neighbor lost");
 -        bgp_store_error(p, NULL, BE_MISC, BEM_NEIGHBOR_LOST);
 -        /* Perhaps also run bgp_update_startup_delay(p)? */
 -        bgp_stop(p, 0, NULL, 0);
 -      }
 +      BGP_TRACE(D_EVENTS, "Neighbor lost");
 +      bgp_store_error(p, NULL, BE_MISC, BEM_NEIGHBOR_LOST);
 +      /* Perhaps also run bgp_update_startup_delay(p)? */
-       bgp_stop(p, 0);
++      bgp_stop(p, 0, NULL, 0);
      }
 +  }
    else if (p->cf->check_link && !(n->iface->flags & IF_LINK_UP))
 +  {
 +    if (!prepare)
      {
 -      if (!prepare)
 -        {
 -        BGP_TRACE(D_EVENTS, "Link down");
 -        bgp_store_error(p, NULL, BE_MISC, BEM_LINK_DOWN);
 -        if (ps == PS_UP)
 -          bgp_update_startup_delay(p);
 -        bgp_stop(p, 0, NULL, 0);
 -      }
 +      BGP_TRACE(D_EVENTS, "Link down");
 +      bgp_store_error(p, NULL, BE_MISC, BEM_LINK_DOWN);
 +      if (ps == PS_UP)
 +      bgp_update_startup_delay(p);
-       bgp_stop(p, 0);
++      bgp_stop(p, 0, NULL, 0);
      }
 +  }
    else
 +  {
 +    if (prepare)
      {
 -      if (prepare)
 -      {
 -        BGP_TRACE(D_EVENTS, "Neighbor ready");
 -        bgp_start_neighbor(p);
 -      }
 +      BGP_TRACE(D_EVENTS, "Neighbor ready");
 +      bgp_start_neighbor(p);
      }
 +  }
  }
  
  static void
@@@ -1156,13 -1004,13 +1156,13 @@@ bgp_bfd_notify(struct bfd_request *req
    int ps = p->p.proto_state;
  
    if (req->down && ((ps == PS_START) || (ps == PS_UP)))
 -    {
 -      BGP_TRACE(D_EVENTS, "BFD session down");
 -      bgp_store_error(p, NULL, BE_MISC, BEM_BFD_DOWN);
 -      if (ps == PS_UP)
 -      bgp_update_startup_delay(p);
 -      bgp_stop(p, 0, NULL, 0);
 -    }
 +  {
 +    BGP_TRACE(D_EVENTS, "BFD session down");
 +    bgp_store_error(p, NULL, BE_MISC, BEM_BFD_DOWN);
 +    if (ps == PS_UP)
 +      bgp_update_startup_delay(p);
-     bgp_stop(p, 0);
++    bgp_stop(p, 0, NULL, 0);
 +  }
  }
  
  static void
@@@ -1352,43 -1205,45 +1356,45 @@@ bgp_shutdown(struct proto *P
    BGP_TRACE(D_EVENTS, "Shutdown requested");
  
    switch (P->down_code)
 -    {
 -    case PDC_CF_REMOVE:
 -    case PDC_CF_DISABLE:
 -      subcode = 3; // Errcode 6, 3 - peer de-configured
 -      break;
 -
 -    case PDC_CF_RESTART:
 -      subcode = 6; // Errcode 6, 6 - other configuration change
 -      break;
 -
 -    case PDC_CMD_DISABLE:
 -    case PDC_CMD_SHUTDOWN:
 -      subcode = 2; // Errcode 6, 2 - administrative shutdown
 -      message = P->message;
 -      break;
 -
 -    case PDC_CMD_RESTART:
 -      subcode = 4; // Errcode 6, 4 - administrative reset
 -      message = P->message;
 -      break;
 -
 -    case PDC_RX_LIMIT_HIT:
 -    case PDC_IN_LIMIT_HIT:
 -      subcode = 1; // Errcode 6, 1 - max number of prefixes reached
 -      /* log message for compatibility */
 -      log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name);
 -      goto limit;
 -
 -    case PDC_OUT_LIMIT_HIT:
 -      subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown
 -
 -    limit:
 -      bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED);
 -      if (proto_restart)
 -      bgp_update_startup_delay(p);
 -      else
 -      p->startup_delay = 0;
 -      goto done;
 -    }
 +  {
 +  case PDC_CF_REMOVE:
 +  case PDC_CF_DISABLE:
 +    subcode = 3; // Errcode 6, 3 - peer de-configured
 +    break;
 +
 +  case PDC_CF_RESTART:
 +    subcode = 6; // Errcode 6, 6 - other configuration change
 +    break;
 +
 +  case PDC_CMD_DISABLE:
 +  case PDC_CMD_SHUTDOWN:
 +    subcode = 2; // Errcode 6, 2 - administrative shutdown
++    message = P->message;
 +    break;
 +
 +  case PDC_CMD_RESTART:
 +    subcode = 4; // Errcode 6, 4 - administrative reset
++    message = P->message;
 +    break;
 +
 +  case PDC_RX_LIMIT_HIT:
 +  case PDC_IN_LIMIT_HIT:
 +    subcode = 1; // Errcode 6, 1 - max number of prefixes reached
 +    /* log message for compatibility */
 +    log(L_WARN "%s: Route limit exceeded, shutting down", p->p.name);
 +    goto limit;
 +
 +  case PDC_OUT_LIMIT_HIT:
 +    subcode = proto_restart ? 4 : 2; // Administrative reset or shutdown
 +
 +  limit:
 +    bgp_store_error(p, NULL, BE_AUTO_DOWN, BEA_ROUTE_LIMIT_EXCEEDED);
 +    if (proto_restart)
 +      bgp_update_startup_delay(p);
 +    else
 +      p->startup_delay = 0;
 +    goto done;
 +  }
  
    bgp_store_error(p, NULL, BE_MAN_DOWN, 0);
    p->startup_delay = 0;
@@@ -1780,13 -1448,13 +1800,13 @@@ bgp_error(struct bgp_conn *c, uint code
    c->notify_subcode = subcode;
    c->notify_data = data;
    c->notify_size = (len > 0) ? len : 0;
 -  bgp_schedule_packet(c, PKT_NOTIFICATION);
 +  bgp_schedule_packet(c, NULL, PKT_NOTIFICATION);
  
    if (code != 6)
 -    {
 -      bgp_update_startup_delay(p);
 -      bgp_stop(p, 0, NULL, 0);
 -    }
 +  {
 +    bgp_update_startup_delay(p);
-     bgp_stop(p, 0);
++    bgp_stop(p, 0, NULL, 0);
 +  }
  }
  
  /**
diff --cc proto/bgp/bgp.h
index 3d940c22a36f7f2a1cef4ac87596ab8b32949387,22a150ab7e592110da2aba1c593f4c1b21893f7b..40c4b3f0fc90fe7a4f8d0cd8cef170ae863eb516
@@@ -433,11 -208,11 +433,11 @@@ void bgp_conn_enter_established_state(s
  void bgp_conn_enter_close_state(struct bgp_conn *conn);
  void bgp_conn_enter_idle_state(struct bgp_conn *conn);
  void bgp_handle_graceful_restart(struct bgp_proto *p);
 -void bgp_graceful_restart_done(struct bgp_proto *p);
 -void bgp_refresh_begin(struct bgp_proto *p);
 -void bgp_refresh_end(struct bgp_proto *p);
 +void bgp_graceful_restart_done(struct bgp_channel *c);
 +void bgp_refresh_begin(struct bgp_channel *c);
 +void bgp_refresh_end(struct bgp_channel *c);
  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_stop(struct bgp_proto *p, uint subcode, byte *data, uint len);
  
  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);
index 0e9747462aaa45b766fa1da219c937d2df78d378,af3b15b59ca46a28c36829e2cf51993011ac2428..038e89f9bd53ad861a5b45d675f3dea530574ae9
@@@ -2678,12 -1494,37 +2678,37 @@@ bgp_error_dsc(uint code, uint subcode
    return buff;
  }
  
+ /* RFC 8203 - shutdown communication message */
+ static int
+ bgp_handle_message(struct bgp_proto *p, byte *data, uint len, byte **bp)
+ {
+   byte *msg = data + 1;
+   uint msg_len = data[0];
+   uint i;
+   /* Handle zero length message */
+   if (msg_len == 0)
+     return 1;
+   /* Handle proper message */
+   if ((msg_len > 128) && (msg_len + 1 > len))
+     return 0;
+   /* Some elementary cleanup */
+   for (i = 0; i < msg_len; i++)
+     if (msg[i] < ' ')
+       msg[i] = ' ';
+   proto_set_message(&p->p, msg, msg_len);
+   *bp += bsprintf(*bp, ": \"%s\"", p->p.message);
+   return 1;
+ }
  void
 -bgp_log_error(struct bgp_proto *p, u8 class, char *msg, unsigned code, unsigned subcode, byte *data, unsigned len)
 +bgp_log_error(struct bgp_proto *p, u8 class, char *msg, uint code, uint subcode, byte *data, uint len)
  {
-   const byte *name;
-   byte *t, argbuf[36];
+   byte argbuf[256], *t = argbuf;
 -  unsigned i;
 +  uint i;
  
    /* Don't report Cease messages generated by myself */
    if (code == 6 && class == BE_BGP_TX)
@@@ -2727,14 -1579,34 +2761,14 @@@ bgp_rx_notification(struct bgp_conn *co
    bgp_log_error(p, BE_BGP_RX, "Received", code, subcode, pkt+21, len-21);
    bgp_store_error(p, conn, BE_BGP_RX, (code << 16) | subcode);
  
 -#ifndef IPV6
 -  if ((code == 2) && ((subcode == 4) || (subcode == 7))
 -      /* Error related to capability:
 -       * 4 - Peer does not support capabilities at all.
 -       * 7 - Peer request some capability. Strange unless it is IPv6 only peer.
 -       */
 -      && (p->cf->capabilities == 2)
 -      /* Capabilities are not explicitly enabled or disabled, therefore heuristic is used */
 -      && (conn->start_state == BSS_CONNECT)
 -      /* Failed connection attempt have used capabilities */
 -      && (p->cf->remote_as <= 0xFFFF))
 -      /* Not possible with disabled capabilities */
 -    {
 -      /* We try connect without capabilities */
 -      log(L_WARN "%s: Capability related error received, retry with capabilities disabled", p->p.name);
 -      p->start_state = BSS_CONNECT_NOCAP;
 -      err = 0;
 -    }
 -#endif
 -
    bgp_conn_enter_close_state(conn);
 -  bgp_schedule_packet(conn, PKT_SCHEDULE_CLOSE);
 +  bgp_schedule_packet(conn, NULL, PKT_SCHEDULE_CLOSE);
  
 -  if (err) 
 -    {
 -      bgp_update_startup_delay(p);
 -      bgp_stop(p, 0, NULL, 0);
 -    }
 +  if (err)
 +  {
 +    bgp_update_startup_delay(p);
-     bgp_stop(p, 0);
++    bgp_stop(p, 0, NULL, 0);
 +  }
  }
  
  static void
index 0e43c237caef191602eb841217ee17e6a3f135e4,84a2de0ef027227c2664558330e0b01905000305..37815f0d47f59775001e4aa4fcbe3f2530fa8071
@@@ -55,7 -56,12 +58,8 @@@ radv_proto_item
   | PREFIX radv_prefix { add_tail(&RADV_CFG->pref_list, NODE this_radv_prefix); }
   | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_CFG->rdnss_list, &radv_dns_list); }
   | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_CFG->dnssl_list, &radv_dns_list); }
 - | TRIGGER prefix {
 -     RADV_CFG->trigger_prefix = $2.addr;
 -     RADV_CFG->trigger_pxlen = $2.len;
 -     RADV_CFG->trigger_valid = 1;
 -   }
 + | TRIGGER net_ip6 { RADV_CFG->trigger = $2; }
+  | PROPAGATE ROUTES bool { RADV_CFG->propagate_routes = $3; }
   ;
  
  radv_proto_opts:
@@@ -76,14 -82,18 +80,18 @@@ radv_iface_start
    init_list(&RADV_IFACE->rdnss_list);
    init_list(&RADV_IFACE->dnssl_list);
  
--  RADV_IFACE->min_ra_int = -1; /* undefined */
++  RADV_IFACE->min_ra_int = (u32) -1; /* undefined */
    RADV_IFACE->max_ra_int = DEFAULT_MAX_RA_INT;
    RADV_IFACE->min_delay = DEFAULT_MIN_DELAY;
 -  RADV_IFACE->prefix_linger_time = -1;
 -  RADV_IFACE->route_linger_time = -1;
++  RADV_IFACE->prefix_linger_time = (u32) -1;
++  RADV_IFACE->route_linger_time = (u32) -1;
    RADV_IFACE->current_hop_limit = DEFAULT_CURRENT_HOP_LIMIT;
-   RADV_IFACE->linger_time = DEFAULT_LINGER_TIME;
--  RADV_IFACE->default_lifetime = -1;
++  RADV_IFACE->default_lifetime = (u32) -1;
    RADV_IFACE->default_lifetime_sensitive = 1;
    RADV_IFACE->default_preference = RA_PREF_MEDIUM;
 -  RADV_IFACE->route_lifetime = -1;
++  RADV_IFACE->route_lifetime = (u32) -1;
+   RADV_IFACE->route_lifetime_sensitive = 0;
+   RADV_IFACE->route_preference = RA_PREF_MEDIUM;
  };
  
  radv_iface_item:
   | MIN DELAY expr { RADV_IFACE->min_delay = $3; if ($3 <= 0) cf_error("Min delay must be positive"); }
   | MANAGED bool { RADV_IFACE->managed = $2; }
   | OTHER CONFIG bool { RADV_IFACE->other_config = $3; }
 - | LINK MTU expr { RADV_IFACE->link_mtu = $3; if ($3 < 0) cf_error("Link MTU must be 0 or positive"); }
 - | REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if (($3 < 0) || ($3 > 3600000)) cf_error("Reachable time must be in range 0-3600000"); }
 - | RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; if ($3 < 0) cf_error("Retrans timer must be 0 or positive"); }
 - | CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if (($4 < 0) || ($4 > 255))  cf_error("Current hop limit must be in range 0-255"); }
 + | LINK MTU expr { RADV_IFACE->link_mtu = $3; }
 + | REACHABLE TIME expr { RADV_IFACE->reachable_time = $3; if ($3 > 3600000) cf_error("Reachable time must be in range 0-3600000"); }
 + | RETRANS TIMER expr { RADV_IFACE->retrans_timer = $3; }
-  | LINGER TIME expr { RADV_IFACE->linger_time = $3; if ($3 > 3600) cf_error("Linger time must be in range 0-3600"); }
 + | CURRENT HOP LIMIT expr { RADV_IFACE->current_hop_limit = $4; if ($4 > 255) cf_error("Current hop limit must be in range 0-255"); }
   | DEFAULT LIFETIME expr radv_sensitive {
       RADV_IFACE->default_lifetime = $3;
 -     if (($3 < 0) || ($3 > 9000))  cf_error("Default lifetime must be in range 0-9000");
 -     if ($4 != -1) RADV_IFACE->default_lifetime_sensitive = $4;
 +     if ($3 > 9000)  cf_error("Default lifetime must be in range 0-9000");
 +     if ($4 != (uint) -1) RADV_IFACE->default_lifetime_sensitive = $4;
     }
 -     if ($4 != -1) RADV_IFACE->route_lifetime_sensitive = $4;
+  | ROUTE LIFETIME expr radv_sensitive {
+      RADV_IFACE->route_lifetime = $3;
++     if ($4 != (uint) -1) RADV_IFACE->route_lifetime_sensitive = $4;
+    }
   | DEFAULT PREFERENCE radv_preference { RADV_IFACE->default_preference = $3; }
+  | ROUTE PREFERENCE radv_preference { RADV_IFACE->route_preference = $3; }
+  | PREFIX LINGER TIME expr { RADV_IFACE->prefix_linger_time = $4; }
+  | ROUTE LINGER TIME expr { RADV_IFACE->route_linger_time = $4; }
   | PREFIX radv_prefix { add_tail(&RADV_IFACE->pref_list, NODE this_radv_prefix); }
   | RDNSS { init_list(&radv_dns_list); } radv_rdnss { add_tail_list(&RADV_IFACE->rdnss_list, &radv_dns_list); }
   | DNSSL { init_list(&radv_dns_list); } radv_dnssl { add_tail_list(&RADV_IFACE->dnssl_list, &radv_dns_list); }
@@@ -125,9 -141,18 +139,18 @@@ radv_iface_finish
    if (ic->default_lifetime == (u32) -1)
      ic->default_lifetime = 3 * ic->max_ra_int;
  
+   if (ic->route_lifetime == (u32) -1)
+     ic->route_lifetime = 3 * ic->max_ra_int;
+   if (ic->prefix_linger_time == (u32) -1)
+     ic->prefix_linger_time = 3 * ic->max_ra_int;
+   if (ic->route_linger_time == (u32) -1)
+     ic->route_linger_time = 3 * ic->max_ra_int;
    if ((ic->min_ra_int > 3) &&
        (ic->min_ra_int > (ic->max_ra_int * 3 / 4)))
 -    cf_error("Min RA interval must be at most 3/4 * Max RA interval %d %d", ic->min_ra_int, ic->max_ra_int);
 +    cf_error("Min RA interval must be at most 3/4 * Max RA interval");
  
    if ((ic->default_lifetime > 0) && (ic->default_lifetime < ic->max_ra_int))
      cf_error("Default lifetime must be either 0 or at least Max RA interval");
@@@ -294,7 -331,7 +328,7 @@@ radv_mult
   ;
  
  radv_sensitive:
--   /* empty */ { $$ = -1; }
++   /* empty */ { $$ = (uint) -1; }
   | SENSITIVE bool { $$ = $2; }
   ;
  
index 7c148b7dc94779378b408cf38ce7b4003fc18bdb,7d54a827e7b8aa9b33cefcfb86f288078b1a73c4..b12d3a12f52184a5f9e9f6d1cd8eabff5683bf78
@@@ -70,6 -80,44 +80,44 @@@ struct radv_opt_dnss
    char domain[];
  };
  
 -  u8 px_blocks = (rt->n.pxlen + 63) / 64;
+ static int
+ radv_prepare_route(struct radv_iface *ifa, struct radv_route *rt,
+                  char **buf, char *bufend)
+ {
+   struct radv_proto *p = ifa->ra;
 -  opt->pxlen = rt->n.pxlen;
++  u8 px_blocks = (net6_pxlen(rt->n.addr) + 63) / 64;
+   u8 opt_len = 8 * (1 + px_blocks);
+   if (*buf + opt_len > bufend)
+   {
+     log(L_WARN, "%s: Too many RA options on interface %s",
+       p->p.name, ifa->iface->name);
+     return -1;
+   }
+   uint preference = rt->preference_set ? rt->preference : ifa->cf->route_preference;
+   uint lifetime = rt->lifetime_set ? rt->lifetime : ifa->cf->route_lifetime;
+   uint valid = rt->valid && p->valid && (p->active || !ifa->cf->route_lifetime_sensitive);
+   struct radv_opt_route *opt = (void *) *buf;
+   *buf += opt_len;
+   opt->type = OPT_ROUTE;
+   opt->length = 1 + px_blocks;
 -  ip6_addr px_addr = ip6_hton(rt->n.prefix);
++  opt->pxlen = net6_pxlen(rt->n.addr);
+   opt->flags = preference;
+   opt->lifetime = valid ? htonl(lifetime) : 0;
+   /* Copy the relevant part of the prefix */
 -    ifa->valid_time = MIN(ifa->valid_time, rt->changed + ifa->cf->route_linger_time);
++  ip6_addr px_addr = ip6_hton(net6_prefix(rt->n.addr));
+   memcpy(opt->prefix, &px_addr, 8 * px_blocks);
+   /* Keeping track of first linger timeout */
+   if (!rt->valid)
++    ifa->valid_time = MIN(ifa->valid_time, rt->changed + ifa->cf->route_linger_time S);
+   return 0;
+ }
  static int
  radv_prepare_rdnss(struct radv_iface *ifa, list *rdnss_list, char **buf, char *bufend)
  {
@@@ -228,9 -277,14 +276,13 @@@ radv_prepare_prefix(struct radv_iface *
    op->preferred_lifetime = (ifa->ra->active || !pc->preferred_lifetime_sensitive) ?
      htonl(pc->preferred_lifetime) : 0;
    op->reserved = 0;
 -  op->prefix = prefix->prefix;
 -  ipa_hton(op->prefix);
 +  op->prefix = ip6_hton(px->prefix.prefix);
    *buf += sizeof(*op);
  
 -  if (!prefix->valid)
 -    ifa->valid_time = MIN(ifa->valid_time, prefix->changed + ifa->cf->prefix_linger_time);
+   /* Keeping track of first linger timeout */
++  if (!px->valid)
++    ifa->valid_time = MIN(ifa->valid_time, px->changed + ifa->cf->prefix_linger_time S);
    return 0;
  }
  
@@@ -240,6 -294,6 +292,7 @@@ radv_prepare_ra(struct radv_iface *ifa
    struct radv_proto *p = ifa->ra;
    struct radv_config *cf = (struct radv_config *) (p->p.cf);
    struct radv_iface_config *ic = ifa->cf;
++  btime now = current_time();
  
    char *buf = ifa->sk->tbuf;
    char *bufstart = buf;
      buf += sizeof (*om);
    }
  
-   struct radv_prefix *prefix;
-   WALK_LIST(prefix, ifa->prefixes)
+   /* Keeping track of first linger timeout */
+   ifa->valid_time = TIME_INFINITY;
+   struct radv_prefix *px;
+   WALK_LIST(px, ifa->prefixes)
    {
-     if (radv_prepare_prefix(ifa, prefix, &buf, bufend) < 0)
+     /* Skip invalid prefixes that are past linger timeout but still not pruned */
 -    if (!px->valid && (px->changed + ic->prefix_linger_time <= now))
++    if (!px->valid && ((px->changed + ic->prefix_linger_time S) <= now))
+       continue;
+     if (radv_prepare_prefix(ifa, px, &buf, bufend) < 0)
        goto done;
    }
  
    if (radv_prepare_dnssl(ifa, &ic->dnssl_list, &buf, bufend) < 0)
      goto done;
  
 -    FIB_WALK(&p->routes, n)
+   if (p->fib_up)
+   {
 -      struct radv_route *rt = (void *) n;
 -
++    FIB_WALK(&p->routes, struct radv_route, rt)
+     {
 -      if (!rt->valid && (rt->changed + ic->route_linger_time <= now))
+       /* Skip invalid routes that are past linger timeout but still not pruned */
++      if (!rt->valid && ((rt->changed + ic->route_linger_time S) <= now))
+       continue;
+       if (radv_prepare_route(ifa, rt, &buf, bufend) < 0)
+       goto done;
+     }
+     FIB_WALK_END;
+   }
   done:
    ifa->plen = buf - bufstart;
  }
index e9140115a2d5a295393fa2b3dd98b7e3a36685a1,d53b65e376f6e92964d61349d5c98125e3981bf7..0a2a3e78a4b5e839f126f729e74c82e481c3e452
@@@ -48,34 -52,32 +52,34 @@@ radv_timer(timer *tm
  {
    struct radv_iface *ifa = tm->data;
    struct radv_proto *p = ifa->ra;
++  btime now = current_time();
  
    RADV_TRACE(D_EVENTS, "Timer fired on %s", ifa->iface->name);
  
-   /*
-    * If some dead prefixes expired, regenerate the prefix list and the packet.
-    * We do so by pretending there was a change on the interface.
-    *
-    * This sets the timer, but we replace it just at the end of this function
-    * (replacing a timer is fine).
-    */
-   if (ifa->prefix_expires && (ifa->prefix_expires <= current_time()))
-     radv_iface_notify(ifa, RA_EV_GC);
+   if (ifa->valid_time <= now)
+     radv_invalidate(ifa);
+   if (ifa->prune_time <= now)
+     radv_prune_prefixes(ifa);
  
-   radv_send_ra(ifa, 0);
+   if (p->prune_time <= now)
+     radv_prune_routes(p);
+   radv_send_ra(ifa);
  
    /* Update timer */
-   ifa->last = current_time();
+   ifa->last = now;
 -  unsigned after = ifa->cf->min_ra_int;
 -  after += random() % (ifa->cf->max_ra_int - ifa->cf->min_ra_int + 1);
 +  btime t = ifa->cf->min_ra_int S;
 +  btime r = (ifa->cf->max_ra_int - ifa->cf->min_ra_int) S;
 +  t += random() % (r + 1);
  
    if (ifa->initial)
 +  {
 +    t = MIN(t, MAX_INITIAL_RTR_ADVERT_INTERVAL);
      ifa->initial--;
 +  }
  
 -  if (ifa->initial)
 -    after = MIN(after, MAX_INITIAL_RTR_ADVERT_INTERVAL);
 -
 -  tm_start(ifa->timer, after);
 +  tm_start(ifa->timer, t);
  }
  
  static struct radv_prefix_config default_prefix = {
@@@ -115,8 -120,7 +119,8 @@@ static voi
  radv_prepare_prefixes(struct radv_iface *ifa)
  {
    struct radv_proto *p = ifa->ra;
-   struct radv_iface_config *cf = ifa->cf;
-   struct radv_prefix *pfx;
+   struct radv_prefix *pfx, *next;
++  btime now = current_time();
  
    /* First mark all the prefixes as unused */
    WALK_LIST(pfx, ifa->prefixes)
      existing->cf = pc;
    }
  
-   /*
-    * Garbage-collect the prefixes. If something isn't used, it dies (but isn't
-    * dropped just yet). If something is dead and rots there for long enough,
-    * clean it up.
-    */
-   btime now_ = current_time();
-   btime expires = now_ + cf->linger_time S;
-   btime expires_min = 0;
-   struct radv_prefix *next;
    WALK_LIST_DELSAFE(pfx, next, ifa->prefixes)
    {
-     if (pfx->alive && !pfx->mark)
+     if (pfx->valid && !pfx->mark)
      {
-       RADV_TRACE(D_EVENTS, "Marking prefix %N on %s as dead",
 -      RADV_TRACE(D_EVENTS, "Invalidating prefix %I/$d on %s",
 -               pfx->prefix, pfx->len, ifa->iface->name);
++      RADV_TRACE(D_EVENTS, "Invalidating prefix %N on %s",
 +               pfx->prefix, ifa->iface->name);
  
-       pfx->alive = 0;
-       pfx->expires = expires;
+       pfx->valid = 0;
+       pfx->changed = now;
        pfx->cf = &dead_prefix;
      }
 -  bird_clock_t next = TIME_INFINITY;
 -  bird_clock_t expires = 0;
+   }
+ }
+ static void
+ radv_prune_prefixes(struct radv_iface *ifa)
+ {
+   struct radv_proto *p = ifa->ra;
++  btime now = current_time();
++  btime next = TIME_INFINITY;
++  btime expires = 0;
  
-     if (!pfx->alive)
+   struct radv_prefix *px, *pxn;
+   WALK_LIST_DELSAFE(px, pxn, ifa->prefixes)
+   {
+     if (!px->valid)
      {
-       if (pfx->expires <= now_)
 -      expires = px->changed + ifa->cf->prefix_linger_time;
++      expires = px->changed + ifa->cf->prefix_linger_time S;
+       if (expires <= now)
        {
 -      RADV_TRACE(D_EVENTS, "Removing prefix %I/%d on %s",
 -                 px->prefix, px->len, ifa->iface->name);
 +      RADV_TRACE(D_EVENTS, "Removing prefix %N on %s",
-                  pfx->prefix, ifa->iface->name);
++                 px->prefix, ifa->iface->name);
  
-       rem_node(NODE pfx);
-       mb_free(pfx);
+       rem_node(NODE px);
+       mb_free(px);
        }
        else
-       {
-       /* Find minimum expiration time */
-       if (!expires_min || (pfx->expires < expires_min))
-         expires_min = pfx->expires;
-       }
+       next = MIN(next, expires);
      }
    }
  
@@@ -232,11 -235,14 +240,9 @@@ radv_iface_notify(struct radv_iface *if
      break;
    }
  
-   radv_prepare_prefixes(ifa);
    /* Update timer */
 -  unsigned delta = now - ifa->last;
 -  unsigned after = 0;
 -
 -  if (delta < ifa->cf->min_delay)
 -    after = ifa->cf->min_delay - delta;
 -
 -  tm_start(ifa->timer, after);
 +  btime t = ifa->last + ifa->cf->min_delay S - current_time();
 +  tm_start(ifa->timer, t);
  }
  
  static void
@@@ -289,8 -305,8 +294,9 @@@ radv_iface_new(struct radv_proto *p, st
    ifa->ra = p;
    ifa->cf = cf;
    ifa->iface = iface;
 +  ifa->addr = iface->llv6;
    init_list(&ifa->prefixes);
+   ifa->prune_time = TIME_INFINITY;
  
    add_tail(&p->iface_list, NODE ifa);
  
@@@ -401,7 -420,7 +410,7 @@@ radv_import_control(struct proto *P, rt
  }
  
  static void
- radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs UNUSED)
 -radv_rt_notify(struct proto *P, rtable *tbl UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs)
++radv_rt_notify(struct proto *P, struct channel *ch UNUSED, net *n, rte *new, rte *old UNUSED, ea_list *attrs)
  {
    struct radv_proto *p = (struct radv_proto *) P;
    struct radv_config *cf = (struct radv_config *) (P->cf);
        RADV_TRACE(D_EVENTS, "Suppressed");
  
      radv_iface_notify_all(p, RA_EV_CHANGE);
 -      log(L_WARN "%s: Invalid ra_preference value %u on route %I/%d",
 -        p->p.name, preference, n->n.prefix, n->n.pxlen);
+     return;
+   }
+   if (!cf->propagate_routes)
+     return;
+   /*
+    * Some other route we want to send (or stop sending). Update the cache,
+    * with marking a removed one as dead or creating a new one as needed.
+    *
+    * And yes, we exclude the trigger route on purpose.
+    */
+   if (new)
+   {
+     /* Update */
+     ea = ea_find(attrs, EA_RA_PREFERENCE);
+     uint preference = ea ? ea->u.data : RA_PREF_MEDIUM;
+     uint preference_set = !!ea;
+     ea = ea_find(attrs, EA_RA_LIFETIME);
+     uint lifetime = ea ? ea->u.data : 0;
+     uint lifetime_set = !!ea;
+     if ((preference != RA_PREF_LOW) &&
+       (preference != RA_PREF_MEDIUM) &&
+       (preference != RA_PREF_HIGH))
+     {
 -    rt = fib_get(&p->routes, &n->n.prefix, n->n.pxlen);
++      log(L_WARN "%s: Invalid ra_preference value %u on route %N",
++        p->p.name, preference, n->n.addr);
+       preference = RA_PREF_MEDIUM;
+       preference_set = 1;
+       lifetime = 0;
+       lifetime_set = 1;
+     }
 -    rt->changed = now;
++    rt = fib_get(&p->routes, n->n.addr);
+     /* Ignore update if nothing changed */
+     if (rt->valid &&
+       (rt->preference == preference) &&
+       (rt->preference_set == preference_set) &&
+       (rt->lifetime == lifetime) &&
+       (rt->lifetime_set == lifetime_set))
+       return;
+     if (p->routes.entries == 18)
+       log(L_WARN "%s: More than 17 routes exported to RAdv", p->p.name);
+     rt->valid = 1;
 -    rt = fib_find(&p->routes, &n->n.prefix, n->n.pxlen);
++    rt->changed = current_time();
+     rt->preference = preference;
+     rt->preference_set = preference_set;
+     rt->lifetime = lifetime;
+     rt->lifetime_set = lifetime_set;
+   }
+   else
+   {
+     /* Withdraw */
 -    rt->changed = now;
++    rt = fib_find(&p->routes, n->n.addr);
+     if (!rt || !rt->valid)
+       return;
+     /* Invalidate the route */
+     rt->valid = 0;
 -    bird_clock_t expires = rt->changed + cf->max_linger_time;
++    rt->changed = current_time();
+     /* Invalidated route will be pruned eventually */
++    btime expires = rt->changed + cf->max_linger_time S;
+     p->prune_time = MIN(p->prune_time, expires);
    }
 -  bird_clock_t next = TIME_INFINITY;
 -  bird_clock_t expires = 0;
+   radv_iface_notify_all(p, RA_EV_CHANGE);
+ }
+ /*
+  * Cleans up all the dead routes that expired and schedules itself to be run
+  * again if there are more routes waiting for expiration.
+  */
+ static void
+ radv_prune_routes(struct radv_proto *p)
+ {
+   struct radv_config *cf = (struct radv_config *) (p->p.cf);
 -  FIB_ITERATE_START(&p->routes, &fit, node)
++  btime now = current_time();
++  btime next = TIME_INFINITY;
++  btime expires = 0;
+   /* Should not happen */
+   if (!p->fib_up)
+     return;
+   struct fib_iterator fit;
+   FIB_ITERATE_INIT(&fit, &p->routes);
+ again:
 -    struct radv_route *rt = (void *) node;
 -
++  FIB_ITERATE_START(&p->routes, &fit, struct radv_route, rt)
+   {
 -      expires = rt->changed + cf->max_linger_time;
+     if (!rt->valid)
+     {
 -      FIB_ITERATE_PUT(&fit, node);
 -      fib_delete(&p->routes, node);
++      expires = rt->changed + cf->max_linger_time S;
+       /* Delete expired nodes */
+       if (expires <= now)
+       {
 -  FIB_ITERATE_END(node);
++      FIB_ITERATE_PUT(&fit);
++      fib_delete(&p->routes, rt);
+       goto again;
+       }
+       else
+       next = MIN(next, expires);
+     }
+   }
++  FIB_ITERATE_END;
+   p->prune_time = next;
  }
  
  static int
@@@ -460,6 -587,21 +587,22 @@@ radv_init(struct proto_config *CF
    return P;
  }
  
 -    fib_init(&p->routes, p->p.pool, sizeof(struct radv_route), 4, NULL);
+ static void
+ radv_set_fib(struct radv_proto *p, int up)
+ {
+   if (up == p->fib_up)
+     return;
+   if (up)
++    fib_init(&p->routes, p->p.pool, NET_IP6, sizeof(struct radv_route),
++           OFFSETOF(struct radv_route, n), 4, NULL);
+   else
+     fib_free(&p->routes);
+   p->fib_up = up;
+   p->prune_time = TIME_INFINITY;
+ }
  static int
  radv_start(struct proto *P)
  {
    struct radv_config *cf = (struct radv_config *) (P->cf);
  
    init_list(&(p->iface_list));
 -  p->active = !cf->trigger_valid;
+   p->valid = 1;
 +  p->active = !radv_trigger_valid(cf);
  
+   p->fib_up = 0;
+   radv_set_fib(p, cf->propagate_routes);
+   p->prune_time = TIME_INFINITY;
    return PS_UP;
  }
  
@@@ -492,26 -644,22 +645,25 @@@ radv_shutdown(struct proto *P
  }
  
  static int
 -radv_reconfigure(struct proto *P, struct proto_config *c)
 +radv_reconfigure(struct proto *P, struct proto_config *CF)
  {
    struct radv_proto *p = (struct radv_proto *) P;
-   // struct radv_config *old = (struct radv_config *) (p->cf);
+   struct radv_config *old = (struct radv_config *) (P->cf);
 -  struct radv_config *new = (struct radv_config *) c;
 +  struct radv_config *new = (struct radv_config *) CF;
  
-   /*
-    * The question is why there is a reconfigure function for RAdv if
-    * it has almost none internal state so restarting the protocol
-    * would probably suffice. One small reason is that restarting the
-    * protocol would lead to sending a RA with Router Lifetime 0
-    * causing nodes to temporary remove their default routes.
-    */
 -  P->cf = c; /* radv_check_active() requires proper P->cf */
 +  if (!proto_configure_channel(P, &P->main_channel, proto_cf_main_channel(CF)))
 +    return 0;
 +
 +  P->cf = CF; /* radv_check_active() requires proper P->cf */
    p->active = radv_check_active(p);
  
 -    proto_request_feeding(&p->p);
+   /* Allocate or free FIB */
+   radv_set_fib(p, new->propagate_routes);
+   /* We started to accept routes so we need to refeed them */
+   if (!old->propagate_routes && new->propagate_routes)
++    channel_request_feeding(p->p.main_channel);
    struct iface *iface;
    WALK_LIST(iface, iface_list)
    {
@@@ -577,10 -747,8 +762,11 @@@ radv_get_attr(eattr *a, byte *buf, int 
  struct protocol proto_radv = {
    .name =             "RAdv",
    .template =         "radv%d",
+   .attr_class =               EAP_RADV,
 +  .channel_mask =     NB_IP6,
 +  .proto_size =               sizeof(struct radv_proto),
    .config_size =      sizeof(struct radv_config),
 +  .postconfig =               radv_postconfig,
    .init =             radv_init,
    .start =            radv_start,
    .shutdown =         radv_shutdown,
index 4672e3b2eb4d5855139b4ae1ad0c948736c4a7b2,ab081397af4e30c0381145821ae22ab300d97a4d..66f785a74ad4ca9fa7a71946061788dc26a2e0de
@@@ -51,7 -50,11 +50,9 @@@ struct radv_confi
    list rdnss_list;            /* Global list of RDNSS configs (struct radv_rdnss_config) */
    list dnssl_list;            /* Global list of DNSSL configs (struct radv_dnssl_config) */
  
 -  ip_addr trigger_prefix;     /* Prefix of a trigger route, if defined */
 -  u8 trigger_pxlen;           /* Pxlen of a trigger route, if defined */
 -  u8 trigger_valid;           /* Whether a trigger route is defined */
 +  net_addr trigger;           /* Prefix of a trigger route, if defined */
+   u8 propagate_routes;                /* Do we propagate more specific routes (RFC 4191)? */
+   u32 max_linger_time;                /* Maximum of interface route_linger_time */
  };
  
  struct radv_iface_config
@@@ -114,24 -121,44 +118,45 @@@ struct radv_dnssl_confi
    char *domain;                       /* Domain for DNS search list, in processed form */
  };
  
 -  struct fib_node n;
+ /*
+  * One more specific route as per RFC 4191.
+  *
+  * Note that it does *not* contain the next hop field. The next hop is always
+  * the router sending the advertisment and the more specific route only allows
+  * overriding the preference of the route.
+  */
+ struct radv_route
+ {
 -  bird_clock_t changed;               /* Last time when the route changed */
+   u32 lifetime;                       /* Lifetime from an attribute */
+   u8 lifetime_set;            /* Whether lifetime is defined */
+   u8 preference;              /* Preference of the route, RA_PREF_* */
+   u8 preference_set;          /* Whether preference is defined */
+   u8 valid;                   /* Whethe route is valid or withdrawn */
++  btime changed;              /* Last time when the route changed */
++
++  struct fib_node n;
+ };
  
  struct radv_proto
  {
    struct proto p;
    list iface_list;            /* List of active ifaces */
+   u8 valid;                   /* Router is valid for forwarding, used for shutdown */
    u8 active;                  /* Whether radv is active w.r.t. triggers */
 -  bird_clock_t prune_time;    /* Next time of route table pruning */
+   u8 fib_up;                  /* FIB table (routes) is initialized */
+   struct fib routes;          /* FIB table of specific routes (struct radv_route) */
++  btime prune_time;           /* Next time of route table pruning */
  };
  
  struct radv_prefix            /* One prefix we advertise */
  {
    node n;
 -  ip_addr prefix;
 -  u8 len;
 +  net_addr_ip6 prefix;
 +
-   u8 alive;                   /* Is the prefix alive? If not, we advertise it
+   u8 valid;                   /* Is the prefix valid? If not, we advertise it
                                   with 0 lifetime, so clients stop using it */
    u8 mark;                    /* A temporary mark for processing */
-   btime expires;              /* The time when we drop this prefix from
-                                  advertising. It is valid only if !alive. */
 -  bird_clock_t changed;               /* Last time when the prefix changed */
++  btime changed;              /* Last time when the prefix changed */
    struct radv_prefix_config *cf; /* The config tied to this prefix */
  };
  
@@@ -144,7 -171,8 +169,8 @@@ struct radv_ifac
    struct ifa *addr;           /* Link-local address of iface */
    struct pool *pool;          /* A pool for interface-specific things */
    list prefixes;              /* The prefixes we advertise (struct radv_prefix) */
-   btime prefix_expires;               /* When the soonest prefix expires (0 = none dead) */
 -  bird_clock_t prune_time;    /* Next time of prefix list pruning */
 -  bird_clock_t valid_time;    /* Cached packet is valid until first linger timeout */
++  btime prune_time;           /* Next time of prefix list pruning */
++  btime valid_time;           /* Cached packet is valid until first linger timeout */
  
    timer *timer;
    struct object_lock *lock;