commit 69a8259c5e438f949bd58b1a2f8e1d12a49f9216
Author: Ondrej Zajicek <santiago@crfreenet.org>
Date:   Sun Jan 1 12:02:20 2012 +0100

    Allows sticky link-local neighbors.
    
    Allows using NEF_STICKY neighbors with link-local addresses. This is
    used for static route nexthops, they can be specified like fe80::1%eth0
    .

diff --git a/conf/confbase.Y b/conf/confbase.Y
index 499a770..14893f4 100644
--- a/conf/confbase.Y
+++ b/conf/confbase.Y
@@ -50,6 +50,7 @@ CF_DECLS
   struct f_path_mask *h;
   struct password_item *p;
   struct rt_show_data *ra;
+  struct iface *iface;
   void *g;
   bird_clock_t time;
   struct prefix px;
@@ -65,6 +66,7 @@ CF_DECLS
 %token <a> IPA
 %token <s> SYM
 %token <t> TEXT
+%type <iface> ipa_scope
 
 %type <i> expr bool pxlen
 %type <time> datetime
@@ -140,6 +142,11 @@ ipa:
    }
  ;
 
+ipa_scope:
+   /* empty */ { $$ = NULL; }
+ | '%' SYM { $$ = if_get_by_name($2->name); }
+ ;
+
 prefix:
    ipa pxlen {
      if (!ip_is_prefix($1, $2)) cf_error("Invalid prefix");
diff --git a/nest/iface.c b/nest/iface.c
index bf57a9b..b8b214e 100644
--- a/nest/iface.c
+++ b/nest/iface.c
@@ -424,6 +424,24 @@ if_find_by_name(char *name)
   return NULL;
 }
 
+struct iface *
+if_get_by_name(char *name)
+{
+  struct iface *i;
+
+  if (i = if_find_by_name(name))
+    return i;
+
+  /* No active iface, create a dummy */
+  i = mb_allocz(if_pool, sizeof(struct iface));
+  strncpy(i->name, name, sizeof(i->name)-1);
+  i->flags = IF_SHUTDOWN;
+  init_list(&i->addrs);
+  init_list(&i->neighbors);
+  add_tail(&iface_list, &i->n);
+  return i;
+}
+
 struct ifa *kif_choose_primary(struct iface *i);
 
 static int
diff --git a/nest/iface.h b/nest/iface.h
index 7307844..d6d58ff 100644
--- a/nest/iface.h
+++ b/nest/iface.h
@@ -97,6 +97,7 @@ void if_flush_ifaces(struct proto *p);
 void if_feed_baby(struct proto *);
 struct iface *if_find_by_index(unsigned);
 struct iface *if_find_by_name(char *);
+struct iface *if_get_by_name(char *);
 void ifa_recalc_all_primary_addresses(void);
 
 /* The Neighbor Cache */
@@ -110,11 +111,13 @@ typedef struct neighbor {
   void *data;				/* Protocol-specific data */
   unsigned aux;				/* Protocol-specific data */
   unsigned flags;
-  unsigned scope;			/* Address scope, SCOPE_HOST when it's our own address */
+  int scope;				/* Address scope, -1 for unreachable sticky neighbors,
+					   SCOPE_HOST when it's our own address */
 } neighbor;
 
 #define NEF_STICKY 1
 #define NEF_ONLINK 2
+#define NEF_BIND 4			/* Used internally for neighbors bound to an iface */
 
 neighbor *neigh_find(struct proto *, ip_addr *, unsigned flags);
 neighbor *neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags);
diff --git a/nest/neighbor.c b/nest/neighbor.c
index 67bd32b..506d9bd 100644
--- a/nest/neighbor.c
+++ b/nest/neighbor.c
@@ -133,6 +133,7 @@ neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags)
   if (ifa)
     {
       scope = if_connected(a, ifa);
+      flags |= NEF_BIND;
 
       if ((scope < 0) && (flags & NEF_ONLINK))
 	scope = class & IADDR_SCOPE_MASK;
@@ -160,10 +161,7 @@ neigh_find2(struct proto *p, ip_addr *a, struct iface *ifa, unsigned flags)
     }
   else
     {
-      /* sticky flag does not work for link-local neighbors;
-	 fortunately, we don't use this combination */
       add_tail(&sticky_neigh_list, &n->n);
-      ifa = NULL;
       scope = -1;
     }
   n->iface = ifa;
@@ -235,7 +233,9 @@ neigh_down(neighbor *n)
 {
   DBG("Flushing neighbor %I on %s\n", n->addr, i->name);
   rem_node(&n->if_n);
-  n->iface = NULL;
+  if (! (n->flags & NEF_BIND))
+    n->iface = NULL;
+  n->scope = -1;
   if (n->proto->neigh_notify && n->proto->core_state != FS_FLUSHING)
     n->proto->neigh_notify(n);
   rem_node(&n->n);
@@ -262,7 +262,8 @@ neigh_if_up(struct iface *i)
   int scope;
 
   WALK_LIST_DELSAFE(n, next, sticky_neigh_list)
-    if ((scope = if_connected(&n->addr, i)) >= 0)
+    if ((!n->iface || n->iface == i) &&
+	((scope = if_connected(&n->addr, i)) >= 0))
       neigh_up(n, i, scope);
 }
 
@@ -339,7 +340,7 @@ neigh_prune_one(neighbor *n)
   if (n->proto->proto_state != PS_DOWN)
     return;
   rem_node(&n->n);
-  if (n->iface)
+  if (n->scope >= 0)
     rem_node(&n->if_n);
   sl_free(neigh_slab, n);
 }
diff --git a/proto/bgp/bgp.c b/proto/bgp/bgp.c
index 28396a5..6bd18f0 100644
--- a/proto/bgp/bgp.c
+++ b/proto/bgp/bgp.c
@@ -804,7 +804,7 @@ bgp_start_locked(struct object_lock *lock)
       return;
     }
   
-  if (p->neigh->iface)
+  if (p->neigh->scope > 0)
     bgp_start_neighbor(p);
   else
     BGP_TRACE(D_EVENTS, "Waiting for %I to become my neighbor", cf->remote_ip);
diff --git a/proto/static/config.Y b/proto/static/config.Y
index 621fdf9..f8e84f9 100644
--- a/proto/static/config.Y
+++ b/proto/static/config.Y
@@ -48,11 +48,12 @@ stat_route0: ROUTE prefix {
  ;
 
 stat_multipath1:
-   VIA ipa {
+   VIA ipa ipa_scope {
      last_srt_nh = this_srt_nh;
      this_srt_nh = cfg_allocz(sizeof(struct static_route));
      this_srt_nh->dest = RTD_NONE;
      this_srt_nh->via = $2;
+     this_srt_nh->via_if = $3;
      this_srt_nh->if_name = (void *) this_srt; /* really */
    }
  | stat_multipath1 WEIGHT expr {
@@ -67,9 +68,10 @@ stat_multipath:
  ;
 
 stat_route:
-   stat_route0 VIA ipa {
+   stat_route0 VIA ipa ipa_scope {
       this_srt->dest = RTD_ROUTER;
       this_srt->via = $3;
+      this_srt->via_if = $4;
    }
  | stat_route0 VIA TEXT {
       this_srt->dest = RTD_DEVICE;
diff --git a/proto/static/static.c b/proto/static/static.c
index e5b293c..3323c7e 100644
--- a/proto/static/static.c
+++ b/proto/static/static.c
@@ -138,12 +138,10 @@ static_decide(struct static_config *cf, struct static_route *r)
   /* r->dest != RTD_MULTIPATH, but may be RTD_NONE (part of multipath route)
      the route also have to be valid (r->neigh != NULL) */
 
-  struct iface *ifa = r->neigh->iface;
-
-  if (!ifa)
+  if (r->neigh->scope < 0)
     return 0;
 
-  if (cf->check_link && !(ifa->flags & IF_LINK_UP))
+  if (cf->check_link && !(r->neigh->iface->flags & IF_LINK_UP))
     return 0;
 
   return 1;
@@ -158,7 +156,7 @@ static_add(struct proto *p, struct static_config *cf, struct static_route *r)
     {
     case RTD_ROUTER:
       {
-	struct neighbor *n = neigh_find(p, &r->via, NEF_STICKY);
+	struct neighbor *n = neigh_find2(p, &r->via, r->via_if, NEF_STICKY);
 	if (n)
 	  {
 	    r->chain = n->data;
@@ -187,7 +185,7 @@ static_add(struct proto *p, struct static_config *cf, struct static_route *r)
 
 	for (r2 = r->mp_next; r2; r2 = r2->mp_next)
 	  {
-	    struct neighbor *n = neigh_find(p, &r2->via, NEF_STICKY);
+	    struct neighbor *n = neigh_find2(p, &r2->via, r2->via_if, NEF_STICKY);
 	    if (n)
 	      {
 		r2->chain = n->data;
@@ -385,7 +383,7 @@ static_same_dest(struct static_route *x, struct static_route *y)
   switch (x->dest)
     {
     case RTD_ROUTER:
-      return ipa_equal(x->via, y->via);
+      return ipa_equal(x->via, y->via) && (x->via_if == y->via_if);
 
     case RTD_DEVICE:
       return !strcmp(x->if_name, y->if_name);
@@ -394,7 +392,7 @@ static_same_dest(struct static_route *x, struct static_route *y)
       for (x = x->mp_next, y = y->mp_next;
 	   x && y;
 	   x = x->mp_next, y = y->mp_next)
-	if (!ipa_equal(x->via, y->via))
+	if (!ipa_equal(x->via, y->via) || (x->via_if != y->via_if))
 	  return 0;
       return !x && !y;
 
diff --git a/proto/static/static.h b/proto/static/static.h
index 775743c..eb87dde 100644
--- a/proto/static/static.h
+++ b/proto/static/static.h
@@ -27,6 +27,7 @@ struct static_route {
   int masklen;				/* Mask length */
   int dest;				/* Destination type (RTD_*) */
   ip_addr via;				/* Destination router */
+  struct iface *via_if;			/* Destination iface, for link-local vias */
   struct neighbor *neigh;
   byte *if_name;			/* Name for RTD_DEVICE routes */
   struct static_route *mp_next;		/* Nexthops for RTD_MULTIPATH routes */
