+ [bgpd] Added new route-map set statement: "as-path ignore"
diff --git a/NEWS b/NEWS
index 74d258f..ff28e1a 100644
--- a/NEWS
+++ b/NEWS
@@ -3,6 +3,7 @@
 - [bgpd] 4-byte AS support added
 - [bgpd] MRT format changes to version 2. Those relying on
   bgpd MRT table dumps may need to update their tools.
+- [bgpd] Added new route-map set statement: "as-path ignore"
 
 * Changes in Quagga 0.99.2
 
diff --git a/bgpd/ChangeLog b/bgpd/ChangeLog
index f3b6a8c..7f94565 100644
--- a/bgpd/ChangeLog
+++ b/bgpd/ChangeLog
@@ -1,3 +1,14 @@
+2008-04-10 Denis Ovsienko
+	* bgp_aspath.[ch]: (aspath_filter_exclude) New function allows
+	  filtering out arbitrary ASns from AS_PATH attribute.
+	* bgp_aspath.[ch]: (aspath_print_vty) Accept suffix to let calling
+	  functions signal, if they want the separator or not.
+	* bgp_route.c: (route_vty_out, route_vty_out_tmp, damp_route_vty_out,
+	  flap_route_vty_out, route_vty_out_detail) Fix aspath_print_vty()
+	  calls to have AS_PATH output nicely.
+	* bgp_routemap.c: Introduce "set as-path exclude" route-map command
+	  to employ new filtering functionality.
+
 2008-03-13 Paul Jakma <paul.jakma@sun.com>
 
 	* (various) Remove 0 entries from struct message's, unneeded due to
diff --git a/bgpd/bgp_aspath.c b/bgpd/bgp_aspath.c
index d7e985d..38c9caa 100644
--- a/bgpd/bgp_aspath.c
+++ b/bgpd/bgp_aspath.c
@@ -1225,6 +1225,81 @@
   /* Not reached */
 }
 
+/* Iterate over AS_PATH segments and wipe all occurences of the
+ * listed AS numbers. Hence some segments may lose some or even
+ * all data on the way, the operation is implemented as a smarter
+ * version of aspath_dup(), which allocates memory to hold the new
+ * data, not the original. The new AS path is returned.
+ */
+struct aspath *
+aspath_filter_exclude (struct aspath * source, struct aspath * exclude_list)
+{
+  struct assegment * srcseg, * exclseg, * lastseg;
+  struct aspath * newpath;
+
+  newpath = aspath_new();
+  lastseg = NULL;
+
+  for (srcseg = source->segments; srcseg; srcseg = srcseg->next)
+  {
+    unsigned i, y, newlen = 0, done = 0, skip_as;
+    struct assegment * newseg;
+
+    /* Find out, how much ASns are we going to pick from this segment.
+     * We can't perform filtering right inline, because the size of
+     * the new segment isn't known at the moment yet.
+     */
+    for (i = 0; i < srcseg->length; i++)
+    {
+      skip_as = 0;
+      for (exclseg = exclude_list->segments; exclseg && !skip_as; exclseg = exclseg->next)
+        for (y = 0; y < exclseg->length; y++)
+          if (srcseg->as[i] == exclseg->as[y])
+          {
+            skip_as = 1;
+            // There's no sense in testing the rest of exclusion list, bail out.
+            break;
+          }
+      if (!skip_as)
+        newlen++;
+    }
+    /* newlen is now the number of ASns to copy */
+    if (!newlen)
+      continue;
+
+    /* Actual copying. Allocate memory and iterate once more, performing filtering. */
+    newseg = assegment_new (srcseg->type, newlen);
+    for (i = 0; i < srcseg->length; i++)
+    {
+      skip_as = 0;
+      for (exclseg = exclude_list->segments; exclseg && !skip_as; exclseg = exclseg->next)
+        for (y = 0; y < exclseg->length; y++)
+          if (srcseg->as[i] == exclseg->as[y])
+          {
+            skip_as = 1;
+            break;
+          }
+      if (skip_as)
+        continue;
+      newseg->as[done++] = srcseg->as[i];
+    }
+    /* At his point newlen must be equal to done, and both must be positive. Append
+     * the filtered segment to the gross result. */
+    if (!lastseg)
+      newpath->segments = newseg;
+    else
+      lastseg->next = newseg;
+    lastseg = newseg;
+  }
+  aspath_str_update (newpath);
+  /* We are happy returning even an empty AS_PATH, because the administrator
+   * might expect this very behaviour. There's a mean to avoid this, if necessary,
+   * by having a match rule against certain AS_PATH regexps in the route-map index.
+   */
+  aspath_free (source);
+  return newpath;
+}
+
 /* Add specified AS to the leftmost of aspath. */
 static struct aspath *
 aspath_add_one_as (struct aspath *aspath, as_t asno, u_char type)
@@ -1741,11 +1816,16 @@
 }
 
 /* Printing functions */
+/* Feed the AS_PATH to the vty; the suffix string follows it only in case
+ * AS_PATH wasn't empty.
+ */
 void
-aspath_print_vty (struct vty *vty, const char *format, struct aspath *as)
+aspath_print_vty (struct vty *vty, const char *format, struct aspath *as, const char * suffix)
 {
   assert (format);
   vty_out (vty, format, as->str);
+  if (strlen (as->str) && strlen (suffix))
+    vty_out (vty, "%s", suffix);
 }
 
 static void
diff --git a/bgpd/bgp_aspath.h b/bgpd/bgp_aspath.h
index 3bb616f..d8b41fa 100644
--- a/bgpd/bgp_aspath.h
+++ b/bgpd/bgp_aspath.h
@@ -69,6 +69,7 @@
 extern struct aspath *aspath_dup (struct aspath *);
 extern struct aspath *aspath_aggregate (struct aspath *, struct aspath *);
 extern struct aspath *aspath_prepend (struct aspath *, struct aspath *);
+extern struct aspath *aspath_filter_exclude (struct aspath *, struct aspath *);
 extern struct aspath *aspath_add_seq (struct aspath *, as_t);
 extern struct aspath *aspath_add_confed_seq (struct aspath *, as_t);
 extern int aspath_cmp_left (struct aspath *, struct aspath *);
@@ -81,7 +82,7 @@
 extern struct aspath *aspath_intern (struct aspath *);
 extern void aspath_unintern (struct aspath *);
 extern const char *aspath_print (struct aspath *);
-extern void aspath_print_vty (struct vty *, const char *, struct aspath *);
+extern void aspath_print_vty (struct vty *, const char *, struct aspath *, const char *);
 extern void aspath_print_all_vty (struct vty *);
 extern unsigned int aspath_key_make (void *);
 extern int aspath_loop_check (struct aspath *, as_t);
diff --git a/bgpd/bgp_route.c b/bgpd/bgp_route.c
index 9ddeca5..4fbc4ba 100644
--- a/bgpd/bgp_route.c
+++ b/bgpd/bgp_route.c
@@ -5694,7 +5694,7 @@
     
       /* Print aspath */
       if (attr->aspath)
-        aspath_print_vty (vty, "%s ", attr->aspath);
+        aspath_print_vty (vty, "%s", attr->aspath, " ");
 
       /* Print origin */
       vty_out (vty, "%s", bgp_origin_str[attr->origin]);
@@ -5759,7 +5759,7 @@
       
       /* Print aspath */
       if (attr->aspath)
-        aspath_print_vty (vty, "%s ", attr->aspath);
+        aspath_print_vty (vty, "%s", attr->aspath, " ");
 
       /* Print origin */
       vty_out (vty, "%s", bgp_origin_str[attr->origin]);
@@ -5859,7 +5859,7 @@
     {
       /* Print aspath */
       if (attr->aspath)
-	aspath_print_vty (vty, "%s ", attr->aspath);
+	aspath_print_vty (vty, "%s", attr->aspath, " ");
 
       /* Print origin */
       vty_out (vty, "%s", bgp_origin_str[attr->origin]);
@@ -5922,7 +5922,7 @@
     {
       /* Print aspath */
       if (attr->aspath)
-	aspath_print_vty (vty, "%s ", attr->aspath);
+	aspath_print_vty (vty, "%s", attr->aspath, " ");
 
       /* Print origin */
       vty_out (vty, "%s", bgp_origin_str[attr->origin]);
@@ -5950,7 +5950,7 @@
 	  if (aspath_count_hops (attr->aspath) == 0)
 	    vty_out (vty, "Local");
 	  else
-	    aspath_print_vty (vty, "%s", attr->aspath);
+	    aspath_print_vty (vty, "%s", attr->aspath, "");
 	}
 
       if (CHECK_FLAG (binfo->flags, BGP_INFO_REMOVED))
diff --git a/bgpd/bgp_routemap.c b/bgpd/bgp_routemap.c
index b246e2a..b93b268 100644
--- a/bgpd/bgp_routemap.c
+++ b/bgpd/bgp_routemap.c
@@ -94,6 +94,7 @@
   set ipv6 next-hop global: Done
   set ipv6 next-hop local : Done
   set pathlimit ttl       : Done
+  set as-path exclude     : Done
   match pathlimit as     : Done
 
 */ 
@@ -1274,6 +1275,64 @@
   route_set_aspath_prepend_free,
 };
 
+/* `set as-path exclude ASn' */
+
+/* For ASN exclude mechanism.
+ * Iterate over ASns requested and filter them from the given AS_PATH one by one.
+ * Make a deep copy of existing AS_PATH, but for the first ASn only.
+ */
+static route_map_result_t
+route_set_aspath_exclude (void *rule, struct prefix *dummy, route_map_object_t type, void *object)
+{
+  struct aspath * new_path, * exclude_path;
+  struct bgp_info *binfo;
+
+  if (type == RMAP_BGP)
+  {
+    exclude_path = rule;
+    binfo = object;
+    if (binfo->attr->aspath->refcnt)
+      new_path = aspath_dup (binfo->attr->aspath);
+    else
+      new_path = binfo->attr->aspath;
+    binfo->attr->aspath = aspath_filter_exclude (new_path, exclude_path);
+  }
+  return RMAP_OKAY;
+}
+
+/* FIXME: consider using route_set_aspath_prepend_compile() and
+ * route_set_aspath_prepend_free(), which two below function are
+ * exact clones of.
+ */
+
+/* Compile function for as-path exclude. */
+static void *
+route_set_aspath_exclude_compile (const char *arg)
+{
+  struct aspath *aspath;
+
+  aspath = aspath_str2aspath (arg);
+  if (! aspath)
+    return NULL;
+  return aspath;
+}
+
+static void
+route_set_aspath_exclude_free (void *rule)
+{
+  struct aspath *aspath = rule;
+  aspath_free (aspath);
+}
+
+/* Set ASn exlude rule structure. */
+struct route_map_rule_cmd route_set_aspath_exclude_cmd = 
+{
+  "as-path exclude",
+  route_set_aspath_exclude,
+  route_set_aspath_exclude_compile,
+  route_set_aspath_exclude_free,
+};
+
 /* `set community COMMUNITY' */
 struct rmap_com_set
 {
@@ -2996,7 +3055,7 @@
        set_aspath_prepend_cmd,
        "set as-path prepend .<1-65535>",
        SET_STR
-       "Prepend string for a BGP AS-path attribute\n"
+       "Transform BGP AS_PATH attribute\n"
        "Prepend to the as-path\n"
        "AS number\n")
 {
@@ -3015,7 +3074,7 @@
        "no set as-path prepend",
        NO_STR
        SET_STR
-       "Prepend string for a BGP AS-path attribute\n"
+       "Transform BGP AS_PATH attribute\n"
        "Prepend to the as-path\n")
 {
   int ret;
@@ -3035,10 +3094,56 @@
        "no set as-path prepend .<1-65535>",
        NO_STR
        SET_STR
-       "Prepend string for a BGP AS-path attribute\n"
+       "Transform BGP AS_PATH attribute\n"
        "Prepend to the as-path\n"
        "AS number\n")
 
+DEFUN (set_aspath_exclude,
+       set_aspath_exclude_cmd,
+       "set as-path exclude .<1-65535>",
+       SET_STR
+       "Transform BGP AS-path attribute\n"
+       "Exclude from the as-path\n"
+       "AS number\n")
+{
+  int ret;
+  char *str;
+
+  str = argv_concat (argv, argc, 0);
+  ret = bgp_route_set_add (vty, vty->index, "as-path exclude", str);
+  XFREE (MTYPE_TMP, str);
+  return ret;
+}
+
+DEFUN (no_set_aspath_exclude,
+       no_set_aspath_exclude_cmd,
+       "no set as-path exclude",
+       NO_STR
+       SET_STR
+       "Transform BGP AS_PATH attribute\n"
+       "Exclude from the as-path\n")
+{
+  int ret;
+  char *str;
+
+  if (argc == 0)
+    return bgp_route_set_delete (vty, vty->index, "as-path exclude", NULL);
+
+  str = argv_concat (argv, argc, 0);
+  ret = bgp_route_set_delete (vty, vty->index, "as-path exclude", str);
+  XFREE (MTYPE_TMP, str);
+  return ret;
+}
+
+ALIAS (no_set_aspath_exclude,
+       no_set_aspath_exclude_val_cmd,
+       "no set as-path exclude .<1-65535>",
+       NO_STR
+       SET_STR
+       "Transform BGP AS_PATH attribute\n"
+       "Exclude from the as-path\n"
+       "AS number\n")
+
 DEFUN (set_community,
        set_community_cmd,
        "set community .AA:NN",
@@ -3731,6 +3836,7 @@
   route_map_install_set (&route_set_weight_cmd);
   route_map_install_set (&route_set_metric_cmd);
   route_map_install_set (&route_set_aspath_prepend_cmd);
+  route_map_install_set (&route_set_aspath_exclude_cmd);
   route_map_install_set (&route_set_origin_cmd);
   route_map_install_set (&route_set_atomic_aggregate_cmd);
   route_map_install_set (&route_set_aggregator_as_cmd);
@@ -3799,8 +3905,11 @@
   install_element (RMAP_NODE, &no_set_metric_cmd);
   install_element (RMAP_NODE, &no_set_metric_val_cmd);
   install_element (RMAP_NODE, &set_aspath_prepend_cmd);
+  install_element (RMAP_NODE, &set_aspath_exclude_cmd);
   install_element (RMAP_NODE, &no_set_aspath_prepend_cmd);
   install_element (RMAP_NODE, &no_set_aspath_prepend_val_cmd);
+  install_element (RMAP_NODE, &no_set_aspath_exclude_cmd);
+  install_element (RMAP_NODE, &no_set_aspath_exclude_val_cmd);
   install_element (RMAP_NODE, &set_origin_cmd);
   install_element (RMAP_NODE, &no_set_origin_cmd);
   install_element (RMAP_NODE, &no_set_origin_val_cmd);